From 31d38e6906c503f3144a0880189eedd834ffaed9 Mon Sep 17 00:00:00 2001 From: alexiscatnip Date: Tue, 18 Jan 2022 14:09:40 +0800 Subject: [PATCH] Manual migrate LMV to LMV_1.9.18.429 - added dlls: OggVorbisEncoder.dll, OpenJpegDotNet.dll --- Assets/Plugins/LibreMetaverse/AgentManager.cs | 320 ++-- .../LibreMetaverse/AgentManagerMovement.cs | 32 +- .../LibreMetaverse/AppearanceManager.cs | 121 +- Assets/Plugins/LibreMetaverse/AssetManager.cs | 19 +- .../Assets/Archiving/ArchiveConstants.cs | 1 - .../Assets/Archiving/AssetsArchiver.cs | 2 - .../Assets/Archiving/OarFile.cs | 10 +- .../Assets/Archiving/TarArchiveReader.cs | 1 - .../Assets/Archiving/TarArchiveWriter.cs | 1 - Assets/Plugins/LibreMetaverse/Assets/Asset.cs | 3 - .../Assets/AssetTypes/AssetAnimation.cs | 3 - .../Assets/AssetTypes/AssetBodypart.cs | 3 - .../Assets/AssetTypes/AssetCallingCard.cs | 1 - .../Assets/AssetTypes/AssetClothing.cs | 3 - .../Assets/AssetTypes/AssetGesture.cs | 2 - .../Assets/AssetTypes/AssetLandmark.cs | 1 - .../Assets/AssetTypes/AssetMutable.cs | 3 - .../Assets/AssetTypes/AssetNotecard.cs | 1 - .../Assets/AssetTypes/AssetPrim.cs | 6 +- .../Assets/AssetTypes/AssetScriptBinary.cs | 3 - .../Assets/AssetTypes/AssetScriptText.cs | 3 - .../Assets/AssetTypes/AssetSound.cs | 160 +- .../Assets/AssetTypes/AssetTexture.cs | 54 +- .../Assets/AssetTypes/AssetWearable.cs | 1 - .../Plugins/LibreMetaverse/AvatarManager.cs | 117 +- Assets/Plugins/LibreMetaverse/BitPack.cs | 25 +- .../LibreMetaverse/Capabilities/CapsBase.cs | 100 +- .../LibreMetaverse/Capabilities/CapsClient.cs | 242 ++- .../Capabilities/EventQueueClient.cs | 4 +- Assets/Plugins/LibreMetaverse/Caps.cs | 9 +- .../LibreMetaverse/DirectoryManager.cs | 118 +- Assets/Plugins/LibreMetaverse/EstateTools.cs | 144 +- .../Plugins/LibreMetaverse/FriendsManager.cs | 260 +-- Assets/Plugins/LibreMetaverse/GridClient.cs | 1 - Assets/Plugins/LibreMetaverse/GridManager.cs | 136 +- Assets/Plugins/LibreMetaverse/GroupManager.cs | 6 +- .../LibreMetaverse/Imaging/BakeLayer.cs | 44 +- .../LibreMetaverse/Imaging/ManagedImage.cs | 38 +- .../LibreMetaverse/Imaging/TGALoader.cs | 564 +++---- .../ImportExport/ModelUploader.cs | 27 +- .../Interfaces/IByteBufferPool.cs | 7 +- .../LibreMetaverse/Interfaces/IMessage.cs | 3 - Assets/Plugins/LibreMetaverse/Inventory.cs | 51 +- .../LibreMetaverse/InventoryAISClient.cs | 31 +- .../Plugins/LibreMetaverse/InventoryBase.cs | 132 +- .../LibreMetaverse/InventoryManager.cs | 90 +- .../Plugins/LibreMetaverse/InventoryNode.cs | 41 +- .../MeshmerizerR.cs | 193 ++- .../JSON/JsonData.cs | 8 +- .../JSON/JsonMapper.cs | 25 +- .../JSON/JsonReader.cs | 79 +- .../LLSD/BinaryLLSD.cs | 1 - .../LLSD/XmlLLSD.cs | 257 +-- .../StructuredData.cs | 13 +- .../LibreMetaverse.Types/CRC32.cs | 2 - .../LibreMetaverse.Types/Quaternion.cs | 9 + .../LibreMetaverse.Types/Ray.cs | 40 + .../LibreMetaverse.Types/Ray.cs.meta | 11 + .../LibreMetaverse.Types/TokenBucket.cs | 20 +- .../LibreMetaverse.Types/UUID.cs | 3 +- .../LibreMetaverse.Types/UtilsConversions.cs | 28 +- .../Plugins/LibreMetaverse/LocationParser.cs | 71 + .../LibreMetaverse/LocationParser.cs.meta | 11 + Assets/Plugins/LibreMetaverse/Login.cs | 573 ++++--- .../LibreMetaverse/Messages/LindenMessages.cs | 9 +- .../Plugins/LibreMetaverse/NetworkManager.cs | 23 +- .../Plugins/LibreMetaverse/ObjectManager.cs | 235 ++- .../Plugins/LibreMetaverse/PacketDecoder.cs | 1 - .../Plugins/LibreMetaverse/ParcelManager.cs | 191 +-- Assets/Plugins/LibreMetaverse/Permissions.cs | 11 +- .../LibreMetaverse/PrimMesher/ObjMesh.cs | 2 +- .../LibreMetaverse/PrimMesher/PrimMesher.cs | 2 +- .../LibreMetaverse/PrimMesher/SculptMap.cs | 11 +- .../LibreMetaverse/PrimMesher/SculptMesh.cs | 15 +- .../Primitives/ParticleSystem.cs | 3 +- .../LibreMetaverse/Primitives/Primitive.cs | 6 +- .../LibreMetaverse/Rendering/Rendering.cs | 8 +- Assets/Plugins/LibreMetaverse/Settings.cs | 21 +- Assets/Plugins/LibreMetaverse/SoundManager.cs | 78 +- Assets/Plugins/LibreMetaverse/Sounds.cs | 1 - .../Plugins/LibreMetaverse/TerrainManager.cs | 26 +- .../Plugins/LibreMetaverse/UDPPacketBuffer.cs | 3 +- .../Plugins/LibreMetaverse/Voice/TCPPipe.cs | 29 +- .../LibreMetaverse/Voice/VoiceAccount.cs | 5 +- .../Plugins/LibreMetaverse/Voice/VoiceAux.cs | 7 +- .../LibreMetaverse/Voice/VoiceConnector.cs | 5 +- .../LibreMetaverse/Voice/VoiceControl.cs | 377 ++--- .../LibreMetaverse/Voice/VoiceDefinitions.cs | 34 +- .../LibreMetaverse/Voice/VoiceGateway.cs | 85 +- .../LibreMetaverse/Voice/VoiceManager.cs | 815 +++++++++ .../LibreMetaverse/Voice/VoiceManager.cs.meta | 11 + .../Voice/VoiceManagerBlocking.cs | 144 ++ .../Voice/VoiceManagerBlocking.cs.meta | 11 + .../LibreMetaverse/Voice/VoiceParticipant.cs | 50 +- .../LibreMetaverse/Voice/VoiceSession.cs | 83 +- Assets/Plugins/LibreMetaverse/_Packets_.cs | 4 +- Assets/Plugins/OggVorbisEncoder.dll | Bin 0 -> 895488 bytes Assets/Plugins/OggVorbisEncoder.dll.meta | 33 + Assets/Plugins/OpenJpegDotNet.dll | Bin 0 -> 61952 bytes Assets/Plugins/OpenJpegDotNet.dll.meta | 33 + Assets/Plugins/OpenJpegDotNet.xml | 1487 +++++++++++++++++ Assets/Plugins/OpenJpegDotNet.xml.meta | 7 + 102 files changed, 5580 insertions(+), 2574 deletions(-) create mode 100644 Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Ray.cs create mode 100644 Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Ray.cs.meta create mode 100644 Assets/Plugins/LibreMetaverse/LocationParser.cs create mode 100644 Assets/Plugins/LibreMetaverse/LocationParser.cs.meta create mode 100644 Assets/Plugins/LibreMetaverse/Voice/VoiceManager.cs create mode 100644 Assets/Plugins/LibreMetaverse/Voice/VoiceManager.cs.meta create mode 100644 Assets/Plugins/LibreMetaverse/Voice/VoiceManagerBlocking.cs create mode 100644 Assets/Plugins/LibreMetaverse/Voice/VoiceManagerBlocking.cs.meta create mode 100644 Assets/Plugins/OggVorbisEncoder.dll create mode 100644 Assets/Plugins/OggVorbisEncoder.dll.meta create mode 100644 Assets/Plugins/OpenJpegDotNet.dll create mode 100644 Assets/Plugins/OpenJpegDotNet.dll.meta create mode 100644 Assets/Plugins/OpenJpegDotNet.xml create mode 100644 Assets/Plugins/OpenJpegDotNet.xml.meta diff --git a/Assets/Plugins/LibreMetaverse/AgentManager.cs b/Assets/Plugins/LibreMetaverse/AgentManager.cs index a88a259..04ee90d 100644 --- a/Assets/Plugins/LibreMetaverse/AgentManager.cs +++ b/Assets/Plugins/LibreMetaverse/AgentManager.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2019-2022, Sjofn LLC * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -1047,7 +1048,7 @@ namespace OpenMetaverse remove { lock (m_GroupChatJoinedLock) { m_GroupChatJoined -= value; } } } - /// The event subscribers. null if no subcribers + /// The event subscribers. null if no subscribers private EventHandler m_AlertMessage; /// Raises the AlertMessage event @@ -1270,24 +1271,24 @@ namespace OpenMetaverse /// Your (client) avatars /// "client", "agent", and "avatar" all represent the same thing - public UUID AgentID => id; + public UUID AgentID { get; private set; } /// Temporary assigned to this session, used for /// verifying our identity in packets - public UUID SessionID => sessionID; + public UUID SessionID { get; private set; } /// Shared secret that is never sent over the wire - public UUID SecureSessionID => secureSessionID; + public UUID SecureSessionID { get; private set; } /// Your (client) avatar ID, local to the current region/sim public uint LocalID => localID; /// Where the avatar started at login. Can be "last", "home" /// or a login - public string StartLocation => startLocation; + public string StartLocation { get; private set; } = string.Empty; /// The access level of this agent, usually M, PG or A - public string AgentAccess => agentAccess; + public string AgentAccess { get; private set; } = string.Empty; /// The CollisionPlane of Agent public Vector4 CollisionPlane => collisionPlane; @@ -1301,21 +1302,24 @@ namespace OpenMetaverse /// A which specifies the angular speed, and axis about which an Avatar is rotating. public Vector3 AngularVelocity => angularVelocity; + /// Region handle for 'home' region + public ulong HomeRegionHandle => home.RegionHandle; + /// Position avatar client will goto when login to 'home' or during /// teleport request to 'home' region. - public Vector3 HomePosition => homePosition; + public Vector3 HomePosition => home.Position; /// LookAt point saved/restored with HomePosition - public Vector3 HomeLookAt => homeLookAt; + public Vector3 HomeLookAt => home.LookAt; /// Avatar First Name (i.e. Philip) - public string FirstName => firstName; + public string FirstName { get; private set; } = string.Empty; /// Avatar Last Name (i.e. Linden) - public string LastName => lastName; + public string LastName { get; private set; } = string.Empty; /// LookAt point received with the login response message - public Vector3 LookAt => lookAt; + public Vector3 LookAt { get; private set; } /// Avatar Full Name (i.e. Philip Linden) public string Name @@ -1325,28 +1329,28 @@ namespace OpenMetaverse // This is a fairly common request, so assume the name doesn't // change mid-session and cache the result if (fullName == null || fullName.Length < 2) - fullName = $"{firstName} {lastName}"; + fullName = $"{FirstName} {LastName}"; return fullName; } } /// Gets the health of the agent - public float Health => health; + public float Health { get; private set; } /// Gets the current balance of the agent - public int Balance => balance; + public int Balance { get; private set; } /// Gets the local ID of the prim the agent is sitting on, /// zero if the avatar is not currently sitting public uint SittingOn => sittingOn; /// Gets the of the agents active group. - public UUID ActiveGroup => activeGroup; + public UUID ActiveGroup { get; private set; } /// Gets the Agents powers in the currently active group - public GroupPowers ActiveGroupPowers => activeGroupPowers; + public GroupPowers ActiveGroupPowers { get; private set; } /// Current status message for teleporting - public string TeleportMessage => teleportMessage; + public string TeleportMessage { get; private set; } = string.Empty; /// Current position of the agent as a relative offset from /// the simulator, or the parent object if we are sitting on something @@ -1466,25 +1470,11 @@ namespace OpenMetaverse #region Private Members - private UUID id; - private UUID sessionID; - private UUID secureSessionID; - private string startLocation = string.Empty; - private string agentAccess = string.Empty; - private Vector3 homePosition; - private Vector3 homeLookAt; - private Vector3 lookAt; - private string firstName = string.Empty; - private string lastName = string.Empty; + private HomeInfo home; private string fullName; - private string teleportMessage = string.Empty; private TeleportStatus teleportStat = TeleportStatus.None; private ManualResetEvent teleportEvent = new ManualResetEvent(false); private uint heightWidthGenCounter; - private float health; - private int balance; - private UUID activeGroup; - private GroupPowers activeGroupPowers; private Dictionary gestureCache = new Dictionary(); #endregion Private Members @@ -1549,6 +1539,7 @@ namespace OpenMetaverse Client.Network.RegisterLoginResponseCallback(Network_OnLoginResponse); // Alert Messages Client.Network.RegisterCallback(PacketType.AlertMessage, AlertMessageHandler); + Client.Network.RegisterCallback(PacketType.AgentAlertMessage, AgentAlertMessageHandler); // script control change messages, ie: when an in-world LSL script wants to take control of your agent. Client.Network.RegisterCallback(PacketType.ScriptControlChange, ScriptControlChangeHandler); // Camera Constraint (probably needs to move to AgentManagerCamera TODO: @@ -1578,17 +1569,17 @@ namespace OpenMetaverse { int group_id = message_chunk_group_id; message_chunk_group_id++; - if (message_chunk_group_id > 500) message_chunk_group_id = 1; + if (message_chunk_group_id > 500) { message_chunk_group_id = 1; } string[] chunks = message.SplitBy(900).ToArray(); int chunkid = 1; - foreach(string C in chunks) + foreach(string chunk in chunks) { string chunk_grouping = ""; if(hide_chunk_grouping == false) { - chunk_grouping = "[" + group_id.ToString() + "|" + chunkid.ToString() + "|"+chunks.Length.ToString()+"]"; + chunk_grouping = $"[{group_id}|{chunkid}|{chunks.Length}]"; } - Chat(""+ chunk_grouping+"" + C + "", channel, type, false); + Chat($"{chunk_grouping}{chunk}", channel, type, false); chunkid++; } } @@ -1598,7 +1589,7 @@ namespace OpenMetaverse { AgentData = { - AgentID = id, + AgentID = AgentID, SessionID = Client.Self.SessionID }, ChatData = @@ -1616,6 +1607,26 @@ namespace OpenMetaverse /// Request any instant messages sent while the client was offline to be resent. /// public void RetrieveInstantMessages() + { + Uri offlineMsgsCap = Client.Network.CurrentSim.Caps.CapabilityURI("ReadOfflineMsgs"); + if (offlineMsgsCap == null + || Client.Network.CurrentSim.Caps.CapabilityURI("AcceptFriendship") == null + || Client.Network.CurrentSim.Caps.CapabilityURI("AcceptGroupInvite") == null) + { + // fallback to lludp + RetrieveInstantMessagesLegacy(); + return; + } + + var request = new CapsClient(offlineMsgsCap); + request.OnComplete += OfflineMessageHandlerCallback; + request.GetRequestAsync(Client.Settings.CAPS_TIMEOUT); + } + + /// + /// Request offline instant messages via the legacy LLUDP packet + /// + private void RetrieveInstantMessagesLegacy() { RetrieveInstantMessagesPacket p = new RetrieveInstantMessagesPacket { @@ -1902,7 +1913,7 @@ namespace OpenMetaverse if (request != null) { ChatSessionAcceptInvitation acceptInvite = new ChatSessionAcceptInvitation {SessionID = session_id}; - request.BeginGetResponse(acceptInvite.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(acceptInvite.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); lock (GroupChatSessions.Dictionary) if (!GroupChatSessions.ContainsKey(session_id)) @@ -1941,7 +1952,7 @@ namespace OpenMetaverse startConference.SessionID = tmp_session_id; - request.BeginGetResponse(startConference.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(startConference.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -2490,8 +2501,8 @@ namespace OpenMetaverse /// /// The Objects Simulator Local ID /// - /// - /// + /// + /// public void DeGrab(uint objectLocalID) { DeGrab(objectLocalID, TOUCH_INVALID_TEXCOORD, TOUCH_INVALID_TEXCOORD, @@ -2658,14 +2669,14 @@ namespace OpenMetaverse { AgentData = { - AgentID = id, + AgentID = AgentID, SessionID = Client.Self.SessionID }, MoneyData = { Description = Utils.StringToBytes(description), DestID = target, - SourceID = id, + SourceID = AgentID, TransactionType = (int) type, AggregatePermInventory = 0, AggregatePermNextOwner = 0, @@ -2957,7 +2968,7 @@ namespace OpenMetaverse teleportStat == TeleportStatus.Start || teleportStat == TeleportStatus.Progress) { - teleportMessage = "Teleport timed out."; + TeleportMessage = "Teleport timed out."; teleportStat = TeleportStatus.Failed; } @@ -3004,7 +3015,7 @@ namespace OpenMetaverse } else { - teleportMessage = "Unable to resolve name: " + simName; + TeleportMessage = "Unable to resolve name: " + simName; teleportStat = TeleportStatus.Failed; return false; } @@ -3067,7 +3078,7 @@ namespace OpenMetaverse teleportStat == TeleportStatus.Start || teleportStat == TeleportStatus.Progress) { - teleportMessage = "Teleport timed out."; + TeleportMessage = "Teleport timed out."; teleportStat = TeleportStatus.Failed; } @@ -3109,7 +3120,7 @@ namespace OpenMetaverse } else { - teleportMessage = "CAPS event queue is not running"; + TeleportMessage = "CAPS event queue is not running"; teleportEvent.Set(); teleportStat = TeleportStatus.Failed; } @@ -3153,7 +3164,7 @@ namespace OpenMetaverse { AgentData = { - AgentID = Client.Self.id, + AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, Info = @@ -3236,8 +3247,8 @@ namespace OpenMetaverse { AgentData = { - AgentID = id, - SessionID = sessionID + AgentID = AgentID, + SessionID = SessionID }, PropertiesData = { @@ -3255,7 +3266,7 @@ namespace OpenMetaverse } /// - /// Update agents profile interests + /// Update agent's profile interests /// /// selection of interests from struct public void UpdateInterests(Avatar.Interests interests) @@ -3264,8 +3275,8 @@ namespace OpenMetaverse { AgentData = { - AgentID = id, - SessionID = sessionID + AgentID = AgentID, + SessionID = SessionID }, PropertiesData = { @@ -3280,6 +3291,29 @@ namespace OpenMetaverse Client.Network.SendPacket(aiup); } + /// + /// Update agent's private notes for target avatar + /// + /// target avatar for notes + /// notes to store + public void UpdateProfileNotes(UUID target, string notes) + { + AvatarNotesUpdatePacket anup = new AvatarNotesUpdatePacket + { + AgentData = + { + AgentID = AgentID, + SessionID = SessionID + }, + Data = + { + TargetID = target, + Notes = Utils.StringToBytes(notes) + } + }; + Client.Network.SendPacket(anup); + } + /// /// Set the height and the width of the client window. This is used /// by the server to build a virtual camera frustum for our avatar @@ -3583,7 +3617,7 @@ namespace OpenMetaverse AgentData = { AgentID = Client.Self.AgentID, - SessionID = Client.Self.sessionID + SessionID = Client.Self.SessionID }, Data = {PickID = pickID} }; @@ -3693,7 +3727,7 @@ namespace OpenMetaverse } }; - request.BeginGetResponse(Client.Settings.CAPS_TIMEOUT); + request.GetRequestAsync(Client.Settings.CAPS_TIMEOUT); } catch (Exception ex) { @@ -3731,7 +3765,7 @@ namespace OpenMetaverse NewDisplayName = newName }; - request.BeginGetResponse(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } /// @@ -3750,7 +3784,7 @@ namespace OpenMetaverse }; CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("UpdateAgentLanguage"); - request?.BeginGetResponse(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request?.PostRequestAsync(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } catch (Exception ex) { @@ -3785,15 +3819,15 @@ namespace OpenMetaverse { bool success = true; - if (error == null && result is OSDMap) + if (error == null && result is OSDMap osdMap) { - var map = ((OSDMap)result)["access_prefs"]; - agentAccess = ((OSDMap)map)["max"]; - Logger.Log($"Max maturity access set to {agentAccess}", Helpers.LogLevel.Info, Client ); + var map = osdMap["access_prefs"]; + AgentAccess = ((OSDMap)map)["max"]; + Logger.Log($"Max maturity access set to {AgentAccess}", Helpers.LogLevel.Info, Client ); } else if (error == null) { - Logger.Log($"Max maturity unchanged at {agentAccess}", Helpers.LogLevel.Info, Client); + Logger.Log($"Max maturity unchanged at {AgentAccess}", Helpers.LogLevel.Info, Client); } else { @@ -3803,16 +3837,20 @@ namespace OpenMetaverse if (callback != null) { - try { callback(new AgentAccessEventArgs(success, agentAccess)); } + try { callback(new AgentAccessEventArgs(success, AgentAccess)); } catch { } // *TODO: So gross } }; - OSDMap req = new OSDMap(); - OSDMap prefs = new OSDMap {["max"] = access}; - req["access_prefs"] = prefs; + OSDMap req = new OSDMap + { + ["access_prefs"] = new OSDMap { ["max"] = access } + }; + //OSDMap req = new OSDMap(); + //OSDMap prefs = new OSDMap {["max"] = access}; + //req["access_prefs"] = prefs; - request.BeginGetResponse(req, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(req, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } /// @@ -3851,7 +3889,7 @@ namespace OpenMetaverse var postData = new OSDMap { ["hover_height"] = hoverHeight }; - request.BeginGetResponse(postData, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(postData, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } #endregion Misc @@ -3894,6 +3932,57 @@ namespace OpenMetaverse } } + protected void OfflineMessageHandlerCallback(CapsClient client, OSD response, Exception error) + { + if (error != null) { + Logger.Log($"Failed to retrieve offline messages from the simulator: {error.Message}", + Helpers.LogLevel.Warning); + RetrieveInstantMessagesLegacy(); + return; + } + + if (m_InstantMessage == null) return; // don't bother if we don't have any listeners + + if (!(response is OSDMap respMap) || respMap.Count == 0 || respMap.ContainsKey("messages")) + { + Logger.Log("Failed to retrieve offline messages because the capability returned some goofy shit.", + Helpers.LogLevel.Warning); + RetrieveInstantMessagesLegacy(); + return; + } + + if (respMap["messages"] is OSDArray msgArray) + { + foreach (var osd in msgArray) + { + var msg = (OSDMap)osd; + + InstantMessage message; + message.FromAgentID = msg["from_agent_id"].AsUUID(); + message.FromAgentName = msg["from_agent_name"].AsString(); + message.ToAgentID = msg["to_agent_id"].AsUUID(); + message.RegionID = msg["region_id"].AsUUID(); + message.Dialog = (InstantMessageDialog)msg["dialog"].AsInteger(); + message.IMSessionID = msg["transaction-id"].AsUUID(); + message.Timestamp = new DateTime(msg["timestamp"].AsInteger()); + message.Message = msg["message"].AsString(); + message.Offline = msg.ContainsKey("offline") + ? (InstantMessageOnline)msg["offline"].AsInteger() + : InstantMessageOnline.Offline; + message.ParentEstateID = msg.ContainsKey("parent_estate_id") + ? msg["parent_estate_id"].AsUInteger() : 1; + message.Position = msg.ContainsKey("position") + ? msg["position"].AsVector3() + : new Vector3(msg["local_x"], msg["local_y"], msg["local_z"]); + message.BinaryBucket = msg.ContainsKey("binary_bucket") + ? msg["binary_bucket"].AsBinary() : new byte[] { 0 }; + message.GroupIM = msg.ContainsKey("from_group") && msg["from_group"].AsBoolean(); + + OnInstantMessage(new InstantMessageEventArgs(message, null)); + } + } + } + /// /// Take an incoming Chat packet, auto-parse, and if OnChat is defined call /// that with the appropriate arguments. @@ -4036,7 +4125,7 @@ namespace OpenMetaverse protected void HealthHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; - health = ((HealthMessagePacket)packet).HealthData.Health; + Health = ((HealthMessagePacket)packet).HealthData.Health; } /// Process an incoming packet and raise the appropriate events @@ -4051,17 +4140,17 @@ namespace OpenMetaverse if (p.AgentData.AgentID == simulator.Client.Self.AgentID) { - firstName = Utils.BytesToString(p.AgentData.FirstName); - lastName = Utils.BytesToString(p.AgentData.LastName); - activeGroup = p.AgentData.ActiveGroupID; - activeGroupPowers = (GroupPowers)p.AgentData.GroupPowers; + FirstName = Utils.BytesToString(p.AgentData.FirstName); + LastName = Utils.BytesToString(p.AgentData.LastName); + ActiveGroup = p.AgentData.ActiveGroupID; + ActiveGroupPowers = (GroupPowers)p.AgentData.GroupPowers; if (m_AgentData == null) return; string groupTitle = Utils.BytesToString(p.AgentData.GroupTitle); string groupName = Utils.BytesToString(p.AgentData.GroupName); - OnAgentData(new AgentDataReplyEventArgs(firstName, lastName, activeGroup, groupTitle, activeGroupPowers, groupName)); + OnAgentData(new AgentDataReplyEventArgs(FirstName, LastName, ActiveGroup, groupTitle, ActiveGroupPowers, groupName)); } else { @@ -4080,7 +4169,7 @@ namespace OpenMetaverse if (packet.Type == PacketType.MoneyBalanceReply) { MoneyBalanceReplyPacket reply = (MoneyBalanceReplyPacket)packet; - this.balance = reply.MoneyData.MoneyBalance; + this.Balance = reply.MoneyData.MoneyBalance; if (m_MoneyBalance != null) { @@ -4107,7 +4196,7 @@ namespace OpenMetaverse if (m_Balance != null) { - OnBalance(new BalanceEventArgs(balance)); + OnBalance(new BalanceEventArgs(Balance)); } } @@ -4223,7 +4312,7 @@ namespace OpenMetaverse { TeleportStartPacket start = (TeleportStartPacket)packet; - teleportMessage = "Teleport started"; + TeleportMessage = "Teleport started"; flags = (TeleportFlags)start.Info.TeleportFlags; teleportStat = TeleportStatus.Start; @@ -4233,21 +4322,21 @@ namespace OpenMetaverse { TeleportProgressPacket progress = (TeleportProgressPacket)packet; - teleportMessage = Utils.BytesToString(progress.Info.Message); + TeleportMessage = Utils.BytesToString(progress.Info.Message); flags = (TeleportFlags)progress.Info.TeleportFlags; teleportStat = TeleportStatus.Progress; - Logger.DebugLog($"TeleportProgress received, Message: {teleportMessage}, Flags: {flags}", Client); + Logger.DebugLog($"TeleportProgress received, Message: {TeleportMessage}, Flags: {flags}", Client); } else if (packet.Type == PacketType.TeleportFailed) { TeleportFailedPacket failed = (TeleportFailedPacket)packet; - teleportMessage = Utils.BytesToString(failed.Info.Reason); + TeleportMessage = Utils.BytesToString(failed.Info.Reason); teleportStat = TeleportStatus.Failed; finished = true; - Logger.DebugLog($"TeleportFailed received, Reason: {teleportMessage}", Client); + Logger.DebugLog($"TeleportFailed received, Reason: {TeleportMessage}", Client); } else if (packet.Type == PacketType.TeleportFinish) { @@ -4266,25 +4355,25 @@ namespace OpenMetaverse if (newSimulator != null) { - teleportMessage = "Teleport finished"; + TeleportMessage = "Teleport finished"; teleportStat = TeleportStatus.Finished; Logger.Log($"Moved to new sim {newSimulator}", Helpers.LogLevel.Info, Client); } else { - teleportMessage = "Failed to connect to the new sim after a teleport"; + TeleportMessage = "Failed to connect to the new sim after a teleport"; teleportStat = TeleportStatus.Failed; // We're going to get disconnected now - Logger.Log(teleportMessage, Helpers.LogLevel.Error, Client); + Logger.Log(TeleportMessage, Helpers.LogLevel.Error, Client); } } else if (packet.Type == PacketType.TeleportCancel) { //TeleportCancelPacket cancel = (TeleportCancelPacket)packet; - teleportMessage = "Cancelled"; + TeleportMessage = "Cancelled"; teleportStat = TeleportStatus.Cancelled; finished = true; @@ -4294,7 +4383,7 @@ namespace OpenMetaverse { TeleportLocalPacket local = (TeleportLocalPacket)packet; - teleportMessage = "Teleport finished"; + TeleportMessage = "Teleport finished"; flags = (TeleportFlags)local.Info.TeleportFlags; teleportStat = TeleportStatus.Finished; relativePosition = local.Info.Position; @@ -4308,7 +4397,7 @@ namespace OpenMetaverse if (m_Teleport != null) { - OnTeleport(new TeleportEventArgs(teleportMessage, teleportStat, flags)); + OnTeleport(new TeleportEventArgs(TeleportMessage, teleportStat, flags)); } if (finished) teleportEvent.Set(); @@ -4394,17 +4483,16 @@ namespace OpenMetaverse private void Network_OnLoginResponse(bool loginSuccess, bool redirect, string message, string reason, LoginResponseData reply) { - id = reply.AgentID; - sessionID = reply.SessionID; - secureSessionID = reply.SecureSessionID; - firstName = reply.FirstName; - lastName = reply.LastName; - startLocation = reply.StartLocation; - agentAccess = reply.AgentAccess; + AgentID = reply.AgentID; + SessionID = reply.SessionID; + SecureSessionID = reply.SecureSessionID; + FirstName = reply.FirstName; + LastName = reply.LastName; + StartLocation = reply.StartLocation; + AgentAccess = reply.AgentAccess; Movement.Camera.LookDirection(reply.LookAt); - homePosition = reply.HomePosition; - homeLookAt = reply.HomeLookAt; - lookAt = reply.LookAt; + home = reply.Home; + LookAt = reply.LookAt; if (reply.Gestures != null) { @@ -4671,7 +4759,7 @@ namespace OpenMetaverse AgentID = memberID }; - request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -4689,7 +4777,31 @@ namespace OpenMetaverse AlertMessagePacket alert = (AlertMessagePacket)packet; - OnAlertMessage(new AlertMessageEventArgs(Utils.BytesToString(alert.AlertData.Message))); + string message = Utils.BytesToString(alert.AlertData.Message); + + if (alert.AlertInfo.Length > 0) + { + string notificationid = Utils.BytesToString(alert.AlertInfo[0].Message); + OSDMap extra = (alert.AlertInfo[0].ExtraParams != null && alert.AlertInfo[0].ExtraParams.Length > 0) + ? OSDParser.Deserialize(alert.AlertInfo[0].ExtraParams) as OSDMap + : null; + OnAlertMessage(new AlertMessageEventArgs(message, notificationid, extra)); + } + else + { + OnAlertMessage(new AlertMessageEventArgs(message, null, null)); + } + } + + protected void AgentAlertMessageHandler(object sender, PacketReceivedEventArgs e) + { + if (m_AlertMessage == null) return; + Packet packet = e.Packet; + + AgentAlertMessagePacket alert = (AgentAlertMessagePacket)packet; + // HACK: Agent alerts support modal and Generic Alerts do not, but it's all the same for + // my simplified ass right now. + OnAlertMessage(new AlertMessageEventArgs(Utils.BytesToString(alert.AlertData.Message), null, null)); } /// Process an incoming packet and raise the appropriate events @@ -5309,14 +5421,20 @@ namespace OpenMetaverse { /// Get the alert message public string Message { get; } + public string NotificationId { get; } + public OSDMap ExtraParams { get; } /// /// Construct a new instance of the AlertMessageEventArgs class /// - /// The alert message - public AlertMessageEventArgs(string message) + /// user readable message + /// notification id for alert, may be null + /// any extra params in OSD format, may be null + public AlertMessageEventArgs(string message, string notificationid, OSDMap extraparams) { Message = message; + NotificationId = notificationid; + ExtraParams = extraparams; } } diff --git a/Assets/Plugins/LibreMetaverse/AgentManagerMovement.cs b/Assets/Plugins/LibreMetaverse/AgentManagerMovement.cs index bfd42e7..10ac4ed 100644 --- a/Assets/Plugins/LibreMetaverse/AgentManagerMovement.cs +++ b/Assets/Plugins/LibreMetaverse/AgentManagerMovement.cs @@ -27,7 +27,6 @@ using System; using System.Threading; using OpenMetaverse.Packets; -using UnityEngine; namespace OpenMetaverse { @@ -378,10 +377,7 @@ namespace OpenMetaverse } } /// The current value of the agent control flags - public uint AgentControls - { - get { return agentControls; } - } + public uint AgentControls { get; private set; } /// Gets or sets the interval in milliseconds at which /// AgentUpdate packets are sent to the current simulator. Setting @@ -421,11 +417,7 @@ namespace OpenMetaverse } /// Reset movement controls every time we send an update - public bool AutoResetControls - { - get { return autoResetControls; } - set { autoResetControls = value; } - } + public bool AutoResetControls { get; set; } #endregion Properties @@ -460,13 +452,11 @@ namespace OpenMetaverse private bool alwaysRun; private GridClient Client; - private uint agentControls; private int duplicateCount; private AgentState lastState; /// Timer for sending AgentUpdate packets private Timer updateTimer; private int updateInterval; - private bool autoResetControls; /// Default constructor public AgentMovement(GridClient client) @@ -599,8 +589,6 @@ namespace OpenMetaverse /// Simulator to send the update to public void SendUpdate(bool reliable, Simulator simulator) { - Debug.Log("sending movement packet."); - // Since version 1.40.4 of the Linden simulator, sending this update // causes corruption of the agent position in the simulator if (simulator != null && (!simulator.AgentMovementComplete)) @@ -612,7 +600,7 @@ namespace OpenMetaverse Vector3 zAxis = Camera.UpAxis; // Attempted to sort these in a rough order of how often they might change - if (agentControls == 0 && + if (AgentControls == 0 && yAxis == LastCameraYAxis && origin == LastCameraCenter && State == lastState && @@ -655,13 +643,13 @@ namespace OpenMetaverse update.AgentData.CameraUpAxis = zAxis; update.AgentData.Far = Camera.Far; update.AgentData.State = (byte)State; - update.AgentData.ControlFlags = agentControls; + update.AgentData.ControlFlags = AgentControls; update.AgentData.Flags = (byte)Flags; Client.Network.SendPacket(update, simulator); - Logger.DebugLog("sent packet of agent movement!"); + Logger.DebugLog("sent out a packet of agent movement!"); - if (autoResetControls) { + if (AutoResetControls) { ResetControlFlags(); } } @@ -714,20 +702,20 @@ namespace OpenMetaverse private bool GetControlFlag(ControlFlags flag) { - return (agentControls & (uint)flag) != 0; + return (AgentControls & (uint)flag) != 0; } private void SetControlFlag(ControlFlags flag, bool value) { - if (value) agentControls |= (uint)flag; - else agentControls &= ~((uint)flag); + if (value) AgentControls |= (uint)flag; + else AgentControls &= ~((uint)flag); } public void ResetControlFlags() { // Reset all of the flags except for persistent settings like // away, fly, mouselook, and crouching - agentControls &= + AgentControls &= (uint)(ControlFlags.AGENT_CONTROL_AWAY | ControlFlags.AGENT_CONTROL_FLY | ControlFlags.AGENT_CONTROL_MOUSELOOK | diff --git a/Assets/Plugins/LibreMetaverse/AppearanceManager.cs b/Assets/Plugins/LibreMetaverse/AppearanceManager.cs index 3f84da4..a05c21a 100644 --- a/Assets/Plugins/LibreMetaverse/AppearanceManager.cs +++ b/Assets/Plugins/LibreMetaverse/AppearanceManager.cs @@ -30,7 +30,6 @@ using System.Collections.Generic; using System.Threading; using System.Linq; using System.Threading.Tasks; -using LibreMetaverse; using Microsoft.Collections.Extensions; using OpenMetaverse.Packets; using OpenMetaverse.Imaging; @@ -367,7 +366,7 @@ namespace OpenMetaverse /// Visual parameters last sent to the sim public byte[] MyVisualParameters; - + /// Textures about this client sent to the sim public Primitive.TextureEntry MyTextures; @@ -414,7 +413,7 @@ namespace OpenMetaverse { MaxDegreeOfParallelism = MAX_CONCURRENT_DOWNLOADS }; - + #endregion Private Members /// @@ -485,7 +484,7 @@ namespace OpenMetaverse // This is the first time setting appearance, run through the entire sequence AppearanceThread = new Thread( - delegate() + delegate () { var cancellationToken = CancellationTokenSource.Token; bool success = true; @@ -495,7 +494,7 @@ namespace OpenMetaverse { // Set all of the baked textures to UUID.Zero to force rebaking for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++) - Textures[(int) BakeTypeToAgentTextureIndex((BakeType) bakedIndex)].TextureID = UUID.Zero; + Textures[(int)BakeTypeToAgentTextureIndex((BakeType)bakedIndex)].TextureID = UUID.Zero; } // FIXME: we really need to make this better... @@ -700,7 +699,7 @@ namespace OpenMetaverse new AgentCachedTexturePacket.WearableDataBlock { ID = hash, - TextureIndex = (byte) bakedIndex + TextureIndex = (byte)bakedIndex }; hashes.Add(block); @@ -737,8 +736,8 @@ namespace OpenMetaverse [Obsolete] public UUID GetWearableAsset(WearableType type) { - return Wearables.TryGetValue(type, out var wearableList) - ? wearableList.First().AssetID + return Wearables.TryGetValue(type, out var wearableList) + ? wearableList.First().AssetID : UUID.Zero; } @@ -804,7 +803,8 @@ namespace OpenMetaverse ItemID = wearableItem.UUID, WearableType = wearableItem.WearableType }; - if (replace || wearableItem.AssetType == AssetType.Bodypart) { + if (replace || wearableItem.AssetType == AssetType.Bodypart) + { // Dump everything from the key Wearables.Remove(wearableItem.WearableType); } @@ -830,7 +830,7 @@ namespace OpenMetaverse /// Wearable to be removed from the outfit public void RemoveFromOutfit(InventoryItem wearableItem) { - List wearableItems = new List {wearableItem}; + List wearableItems = new List { wearableItem }; RemoveFromOutfit(wearableItems); } @@ -915,7 +915,7 @@ namespace OpenMetaverse for (int i = 0; i < WEARABLE_COUNT; i++) { WearableType wearableType = (WearableType)i; - if (WearableTypeToAssetType(wearableType) == AssetType.Bodypart + if (WearableTypeToAssetType(wearableType) == AssetType.Bodypart && !Wearables.ContainsKey(wearableType)) { needsCurrentWearables = true; @@ -998,8 +998,8 @@ namespace OpenMetaverse } /// - /// Calls either or - /// depending on the value of + /// Calls either or + /// depending on the value of /// replaceItems /// /// List of wearable inventory items to add @@ -1060,15 +1060,15 @@ namespace OpenMetaverse { AttachmentPt = replace - ? (byte) attachment.AttachmentPoint - : (byte) (ATTACHMENT_ADD | (byte) attachment.AttachmentPoint), - EveryoneMask = (uint) attachment.Permissions.EveryoneMask, - GroupMask = (uint) attachment.Permissions.GroupMask, - ItemFlags = (uint) attachment.Flags, + ? (byte)attachment.AttachmentPoint + : (byte)(ATTACHMENT_ADD | (byte)attachment.AttachmentPoint), + EveryoneMask = (uint)attachment.Permissions.EveryoneMask, + GroupMask = (uint)attachment.Permissions.GroupMask, + ItemFlags = (uint)attachment.Flags, ItemID = attachment.UUID, Name = Utils.StringToBytes(attachment.Name), Description = Utils.StringToBytes(attachment.Description), - NextOwnerMask = (uint) attachment.Permissions.NextOwnerMask, + NextOwnerMask = (uint)attachment.Permissions.NextOwnerMask, OwnerID = attachment.OwnerID }; @@ -1081,14 +1081,14 @@ namespace OpenMetaverse InventoryObject attachment = (InventoryObject)attachments[i]; attachmentsPacket.ObjectData[i] = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock { - AttachmentPt = replace ? (byte) 0 : ATTACHMENT_ADD, - EveryoneMask = (uint) attachment.Permissions.EveryoneMask, - GroupMask = (uint) attachment.Permissions.GroupMask, - ItemFlags = (uint) attachment.Flags, + AttachmentPt = replace ? (byte)0 : ATTACHMENT_ADD, + EveryoneMask = (uint)attachment.Permissions.EveryoneMask, + GroupMask = (uint)attachment.Permissions.GroupMask, + ItemFlags = (uint)attachment.Flags, ItemID = attachment.UUID, Name = Utils.StringToBytes(attachment.Name), Description = Utils.StringToBytes(attachment.Description), - NextOwnerMask = (uint) attachment.Permissions.NextOwnerMask, + NextOwnerMask = (uint)attachment.Permissions.NextOwnerMask, OwnerID = attachment.OwnerID }; @@ -1365,7 +1365,7 @@ namespace OpenMetaverse WearableType type = (WearableType)i; wearing.WearableData[i] = new AgentIsNowWearingPacket.WearableDataBlock { - WearableType = (byte) i, + WearableType = (byte)i, // This appears to be hacked on SL server side to support multi-layers ItemID = Wearables.ContainsKey(type) ? (Wearables[type].First()?.ItemID ?? UUID.Zero) @@ -1414,9 +1414,12 @@ namespace OpenMetaverse WearableType = wearableItem.WearableType }; // Body cannot be layered. Overwrite when multiple are selected. - if (wearableItem.AssetType == AssetType.Bodypart) { + if (wearableItem.AssetType == AssetType.Bodypart) + { bodyparts[wearableItem.WearableType] = wd; - } else { + } + else + { newWearables.Add(wearableItem.WearableType, wd); } } @@ -1428,7 +1431,7 @@ namespace OpenMetaverse } // heavy handed body part sanity check - if (newWearables.ContainsKey(WearableType.Shape) && + if (newWearables.ContainsKey(WearableType.Shape) && newWearables.ContainsKey(WearableType.Skin) && newWearables.ContainsKey(WearableType.Eyes) && newWearables.ContainsKey(WearableType.Hair)) @@ -1741,7 +1744,7 @@ namespace OpenMetaverse foreach (var wearable in wearables) { if (wearable.Asset == null) continue; - + DecodeWearableParams(wearable, ref Textures); --pendingWearables; } @@ -1750,7 +1753,7 @@ namespace OpenMetaverse return true; Logger.DebugLog("Downloading " + pendingWearables + " wearable assets"); - + Parallel.ForEach(wearables, _parallelOptions, wearable => { @@ -1759,7 +1762,7 @@ namespace OpenMetaverse // Fetch this wearable asset Client.Assets.RequestAsset(wearable.AssetID, wearable.AssetType, true, - delegate(AssetDownload transfer, Asset asset) + delegate (AssetDownload transfer, Asset asset) { if (transfer.Success && asset is AssetWearable assetWearable) { @@ -1862,7 +1865,7 @@ namespace OpenMetaverse AutoResetEvent downloadEvent = new AutoResetEvent(false); Client.Assets.RequestImage(textureId, - delegate(TextureRequestState state, AssetTexture assetTexture) + delegate (TextureRequestState state, AssetTexture assetTexture) { if (state == TextureRequestState.Finished) { @@ -1889,7 +1892,7 @@ namespace OpenMetaverse catch (Exception e) { Logger.Log( - $"Download of texture {textureId} failed with exception {e}", + $"Download of texture {textureId} failed with exception {e}", Helpers.LogLevel.Warning, Client); } } @@ -1911,14 +1914,23 @@ namespace OpenMetaverse { AvatarTextureIndex textureIndex = BakeTypeToAgentTextureIndex((BakeType)bakedIndex); + if (Textures[(int)textureIndex].TextureID == UUID.Zero) { + // If this is the skirt layer and we're not wearing a skirt then skip it if (bakedIndex == (int)BakeType.Skirt && !Wearables.ContainsKey(WearableType.Skirt)) + { + Logger.DebugLog("texture: " + (AvatarTextureIndex)textureIndex + " skipping not attached"); continue; - + } + Logger.DebugLog("texture: " + (AvatarTextureIndex)textureIndex + " is needed adding to pending Bakes"); pendingBakes.Add((BakeType)bakedIndex); } + else + { + Logger.DebugLog("texture: " + (AvatarTextureIndex)textureIndex + " is ready"); + } } if (pendingBakes.Any()) @@ -1939,7 +1951,7 @@ namespace OpenMetaverse { Textures[i].Texture = null; } - + return success; } @@ -1975,7 +1987,10 @@ namespace OpenMetaverse --retries; } - Textures[(int)BakeTypeToAgentTextureIndex(bakeType)].TextureID = newAssetID; + int bakeIndex = (int)BakeTypeToAgentTextureIndex(bakeType); + Logger.DebugLog("Saving back to " + (AvatarTextureIndex)bakeIndex); + + Textures[bakeIndex].TextureID = newAssetID; if (newAssetID == UUID.Zero) { @@ -1997,7 +2012,7 @@ namespace OpenMetaverse var uploadEvent = new AutoResetEvent(false); Client.Assets.RequestUploadBakedTexture(textureData, - delegate(UUID newAssetID) + delegate (UUID newAssetID) { bakeID = newAssetID; uploadEvent.Set(); @@ -2082,11 +2097,11 @@ namespace OpenMetaverse // TODO: create Current Outfit Folder } - OSDMap request = new OSDMap(1) {["cof_version"] = COF.Version}; + OSDMap request = new OSDMap(1) { ["cof_version"] = COF.Version }; string msg = "Setting server side baking failed"; - OSD res = capsRequest.GetResponse(request, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT * 2); + OSD res = capsRequest.PostRequest(request, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT * 2); if (res is OSDMap result) { @@ -2196,16 +2211,16 @@ namespace OpenMetaverse bool found = false; foreach (var wearableList in Wearables) { - if(wearableList.Value.Any(wearable => wearable.Asset != null && - wearable.Asset.Params - .TryGetValue(vp.ParamID, out paramValue))) + if (wearableList.Value.Any(wearable => wearable.Asset != null && + wearable.Asset.Params + .TryGetValue(vp.ParamID, out paramValue))) { found = true; break; } } - + // Try and find this value in our collection of downloaded wearables @@ -2249,13 +2264,16 @@ namespace OpenMetaverse break; } - if (vpIndex <= nrParams) break; + if (vpIndex >= nrParams) break; } MyVisualParameters = new byte[set.VisualParam.Length]; for (int i = 0; i < set.VisualParam.Length; i++) { - MyVisualParameters[i] = set.VisualParam[i].ParamValue; + if (set.VisualParam[i] != null) + { + MyVisualParameters[i] = set.VisualParam[i].ParamValue; + } } #endregion VisualParam @@ -2267,8 +2285,15 @@ namespace OpenMetaverse for (uint i = 0; i < Textures.Length; i++) { Primitive.TextureEntryFace face = te.CreateFace(i); - face.TextureID = Textures[i].TextureID; - Logger.DebugLog("Sending texture entry for " + (AvatarTextureIndex)i + " to " + Textures[i].TextureID, Client); + if (Textures[i].TextureID != UUID.Zero) + { + face.TextureID = Textures[i].TextureID; + Logger.DebugLog("Sending texture entry for " + (AvatarTextureIndex)i + " to " + Textures[i].TextureID, Client); + } + else + { + Logger.DebugLog("Skipping texture entry for " + (AvatarTextureIndex)i + " its null", Client); + } } set.ObjectData.TextureEntry = te.GetBytes(); @@ -2562,7 +2587,7 @@ namespace OpenMetaverse CancellationTokenSource.Dispose(); CancellationTokenSource = null; } - + if (AppearanceThread != null) { AppearanceThread = null; diff --git a/Assets/Plugins/LibreMetaverse/AssetManager.cs b/Assets/Plugins/LibreMetaverse/AssetManager.cs index d2db58f..c86ac65 100644 --- a/Assets/Plugins/LibreMetaverse/AssetManager.cs +++ b/Assets/Plugins/LibreMetaverse/AssetManager.cs @@ -28,7 +28,6 @@ using System; using System.Collections.Generic; using System.Threading; -using System.Net; using OpenMetaverse.Packets; using OpenMetaverse.Assets; using OpenMetaverse.Http; @@ -163,7 +162,7 @@ namespace OpenMetaverse { public UUID ID; public int Size; - public byte[] AssetData = Utils.EmptyBytes; + public byte[] AssetData; public int Transferred; public bool Success; public AssetType AssetType; @@ -637,16 +636,6 @@ namespace OpenMetaverse catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } return; - } else - { - //not in asset cache and not connected. big oopsie :o - if (Client.Network.Connected == false) - { - - Logger.Log("Assetmanager : Cache miss and DISCONNECTED!", Helpers.LogLevel.Error); - return; - } - } // If ViewerAsset capability exists, use that, if not, fallback to UDP (which is obsoleted on Second Life.) @@ -859,8 +848,6 @@ namespace OpenMetaverse /// /// Use UUID.Zero if you do not have the /// asset ID but have all the necessary permissions - /// - /// Whether to prioritize this asset download or not /// /// private void RequestInventoryAssetHTTP(UUID assetID, AssetDownload transfer, AssetReceivedCallback callback) @@ -1143,7 +1130,7 @@ namespace OpenMetaverse Logger.Log("Bake upload failed during asset upload", Helpers.LogLevel.Warning, Client); callback(UUID.Zero); }; - upload.BeginGetResponse(textureData, "application/octet-stream", Client.Settings.CAPS_TIMEOUT); + upload.PostRequestAsync(textureData, "application/octet-stream", Client.Settings.CAPS_TIMEOUT); return; } } @@ -1152,7 +1139,7 @@ namespace OpenMetaverse Logger.Log("Bake upload failed during uploader retrieval", Helpers.LogLevel.Warning, Client); callback(UUID.Zero); }; - request.BeginGetResponse(new OSDMap(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(new OSDMap(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { diff --git a/Assets/Plugins/LibreMetaverse/Assets/Archiving/ArchiveConstants.cs b/Assets/Plugins/LibreMetaverse/Assets/Archiving/ArchiveConstants.cs index e8ff762..b550d8a 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/Archiving/ArchiveConstants.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/Archiving/ArchiveConstants.cs @@ -25,7 +25,6 @@ */ using System.Collections.Generic; -using OpenMetaverse; namespace OpenMetaverse.Assets { diff --git a/Assets/Plugins/LibreMetaverse/Assets/Archiving/AssetsArchiver.cs b/Assets/Plugins/LibreMetaverse/Assets/Archiving/AssetsArchiver.cs index d7b458c..1297956 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/Archiving/AssetsArchiver.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/Archiving/AssetsArchiver.cs @@ -27,9 +27,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Xml; -using OpenMetaverse; namespace OpenMetaverse.Assets { diff --git a/Assets/Plugins/LibreMetaverse/Assets/Archiving/OarFile.cs b/Assets/Plugins/LibreMetaverse/Assets/Archiving/OarFile.cs index 4bfa4ab..318695d 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/Archiving/OarFile.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/Archiving/OarFile.cs @@ -28,10 +28,8 @@ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Text; using System.Xml; using System.Threading; -using OpenMetaverse; namespace OpenMetaverse.Assets { @@ -435,7 +433,10 @@ namespace OpenMetaverse.Assets xtw.WriteElementString("SnapshotID", parcel.SnapshotID.ToString()); xtw.WriteElementString("UserLocation", parcel.UserLocation.ToString()); xtw.WriteElementString("UserLookAt", parcel.UserLookAt.ToString()); - xtw.WriteElementString("Dwell", "0"); + xtw.WriteElementString("Dwell", parcel.Dwell.ToString()); + xtw.WriteElementString("SeeAVs", parcel.SeeAVs.ToString()); + xtw.WriteElementString("AnyAVSounds", parcel.AnyAVSounds.ToString()); + xtw.WriteElementString("GroupAVSounds", parcel.GroupAVSounds.ToString()); xtw.WriteElementString("OtherCleanTime", Convert.ToString(parcel.OtherCleanTime)); xtw.WriteEndElement(); @@ -534,9 +535,8 @@ namespace OpenMetaverse.Assets if (prim.Textures.FaceTextures != null) { - for (int i = 0; i < prim.Textures.FaceTextures.Length; i++) + foreach (var face in prim.Textures.FaceTextures) { - Primitive.TextureEntryFace face = prim.Textures.FaceTextures[i]; if (face != null) textureList[face.TextureID] = face.TextureID; } diff --git a/Assets/Plugins/LibreMetaverse/Assets/Archiving/TarArchiveReader.cs b/Assets/Plugins/LibreMetaverse/Assets/Archiving/TarArchiveReader.cs index b62d6cf..60acc68 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/Archiving/TarArchiveReader.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/Archiving/TarArchiveReader.cs @@ -26,7 +26,6 @@ using System; using System.IO; -using System.Reflection; using System.Text; namespace OpenMetaverse.Assets diff --git a/Assets/Plugins/LibreMetaverse/Assets/Archiving/TarArchiveWriter.cs b/Assets/Plugins/LibreMetaverse/Assets/Archiving/TarArchiveWriter.cs index 01c398b..4d5505b 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/Archiving/TarArchiveWriter.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/Archiving/TarArchiveWriter.cs @@ -25,7 +25,6 @@ */ using System; -using System.Collections.Generic; using System.IO; using System.Text; diff --git a/Assets/Plugins/LibreMetaverse/Assets/Asset.cs b/Assets/Plugins/LibreMetaverse/Assets/Asset.cs index 9e13f74..ec72fc6 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/Asset.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/Asset.cs @@ -24,9 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using OpenMetaverse; - namespace OpenMetaverse.Assets { /// diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetAnimation.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetAnimation.cs index d216151..e7d09d8 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetAnimation.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetAnimation.cs @@ -24,9 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using OpenMetaverse; - namespace OpenMetaverse.Assets { /// diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetBodypart.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetBodypart.cs index a4bb437..29aafd5 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetBodypart.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetBodypart.cs @@ -24,9 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using OpenMetaverse; - namespace OpenMetaverse.Assets { /// diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetCallingCard.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetCallingCard.cs index ef1ca4d..f5aa73b 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetCallingCard.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetCallingCard.cs @@ -25,7 +25,6 @@ */ using System; -using OpenMetaverse; namespace OpenMetaverse.Assets { diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetClothing.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetClothing.cs index 80c6dd7..b3d485f 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetClothing.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetClothing.cs @@ -24,9 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using OpenMetaverse; - namespace OpenMetaverse.Assets { /// diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetGesture.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetGesture.cs index bc1de30..4026298 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetGesture.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetGesture.cs @@ -27,8 +27,6 @@ using System; using System.Collections.Generic; using System.Text; -using System.Text.RegularExpressions; -using OpenMetaverse; namespace OpenMetaverse.Assets { diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetLandmark.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetLandmark.cs index 67813af..42e0906 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetLandmark.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetLandmark.cs @@ -25,7 +25,6 @@ */ using System; -using OpenMetaverse; namespace OpenMetaverse.Assets { diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetMutable.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetMutable.cs index 0933a80..1337ecb 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetMutable.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetMutable.cs @@ -24,9 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using OpenMetaverse; - namespace OpenMetaverse.Assets { /// diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetNotecard.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetNotecard.cs index a53e0ee..f54e5cf 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetNotecard.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetNotecard.cs @@ -28,7 +28,6 @@ using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; -using OpenMetaverse; namespace OpenMetaverse.Assets { diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetPrim.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetPrim.cs index 6ce2aa3..8805207 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetPrim.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetPrim.cs @@ -28,7 +28,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Xml; -using OpenMetaverse; using OpenMetaverse.StructuredData; namespace OpenMetaverse.Assets @@ -782,8 +781,9 @@ namespace OpenMetaverse.Assets if (Items != null) { OSDArray array = new OSDArray(Items.Length); - for (int i = 0; i < Items.Length; i++) - array.Add(Items[i].Serialize()); + foreach (var i in Items) + array.Add(i.Serialize()); + map["items"] = array; } diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetScriptBinary.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetScriptBinary.cs index 59ac8b5..07ce864 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetScriptBinary.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetScriptBinary.cs @@ -24,9 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using OpenMetaverse; - namespace OpenMetaverse.Assets { /// diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetScriptText.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetScriptText.cs index 565ad5d..6683712 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetScriptText.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetScriptText.cs @@ -24,9 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using OpenMetaverse; - namespace OpenMetaverse.Assets { /// diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetSound.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetSound.cs index 5a17ad9..ba88f42 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetSound.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetSound.cs @@ -24,8 +24,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ +using OggVorbisEncoder; using System; -using OpenMetaverse; +using System.IO; namespace OpenMetaverse.Assets { @@ -46,17 +47,162 @@ namespace OpenMetaverse.Assets public AssetSound(UUID assetID, byte[] assetData) : base(assetID, assetData) { + if ((assetID != UUID.Zero) && (assetData.Length > 0)) + { + encodedAudio = true; + } + } + + protected bool encodedAudio = false; + + /// + /// Converts a byte data for a wave PCM file @ 44100 to a OGG encoding + /// + public override void Encode() + { + if (encodedAudio == false) + { + encodedAudio = true; + AssetData = ConvertRawPCMFile(44100, 1, AssetData, PcmSample.SixteenBit, 44100, 2); + } } /// - /// TODO: Encodes a sound file - /// - public override void Encode() { } - - /// - /// TODO: Decode a sound file + /// its already ogg just play it or convert it yourself /// /// true public override bool Decode() { return true; } + + + private static byte[] ConvertRawPCMFile(int outputSampleRate, int outputChannels, byte[] pcmSamples, PcmSample pcmSampleSize, int pcmSampleRate, int pcmChannels) + { + int numPcmSamples = (pcmSamples.Length / (int)pcmSampleSize / pcmChannels); + float pcmDuraton = numPcmSamples / (float)pcmSampleRate; + + int numOutputSamples = (int)(pcmDuraton * outputSampleRate); + //Ensure that samble buffer is aligned to write chunk size + numOutputSamples = (numOutputSamples / WriteBufferSize) * WriteBufferSize; + + float[][] outSamples = new float[outputChannels][]; + + for (int ch = 0; ch < outputChannels; ch++) + { + outSamples[ch] = new float[numOutputSamples]; + } + + for (int sampleNumber = 0; sampleNumber < numOutputSamples; sampleNumber++) + { + float rawSample = 0.0f; + + for (int ch = 0; ch < outputChannels; ch++) + { + int sampleIndex = (sampleNumber * pcmChannels) * (int)pcmSampleSize; + + if (ch < pcmChannels) sampleIndex += (ch * (int)pcmSampleSize); + + switch (pcmSampleSize) + { + case PcmSample.EightBit: + rawSample = ByteToSample(pcmSamples[sampleIndex]); + break; + case PcmSample.SixteenBit: + rawSample = ShortToSample((short)(pcmSamples[sampleIndex + 1] << 8 | pcmSamples[sampleIndex])); + break; + } + + outSamples[ch][sampleNumber] = rawSample; + } + } + + return GenerateFile(outSamples, outputSampleRate, outputChannels); + } + + private static float ByteToSample(short pcmValue) + { + return pcmValue / 128f; + } + + private static float ShortToSample(short pcmValue) + { + return pcmValue / 32768f; + } + + private static readonly int WriteBufferSize = 512; + private static byte[] GenerateFile(float[][] floatSamples, int sampleRate, int channels) + { + MemoryStream outputData = new MemoryStream(); + + // Stores all the static vorbis bitstream settings + var info = VorbisInfo.InitVariableBitRate(channels, sampleRate, 0.5f); + + // set up our packet->stream encoder + var serial = new Random().Next(); + var oggStream = new OggStream(serial); + + // ========================================================= + // HEADER + // ========================================================= + // Vorbis streams begin with three headers; the initial header (with + // most of the codec setup parameters) which is mandated by the Ogg + // bitstream spec. The second header holds any comment fields. The + // third header holds the bitstream codebook. + + var comments = new Comments(); + comments.AddTag("ARTIST", "TEST"); + + var infoPacket = HeaderPacketBuilder.BuildInfoPacket(info); + var commentsPacket = HeaderPacketBuilder.BuildCommentsPacket(comments); + var booksPacket = HeaderPacketBuilder.BuildBooksPacket(info); + + oggStream.PacketIn(infoPacket); + oggStream.PacketIn(commentsPacket); + oggStream.PacketIn(booksPacket); + + // Flush to force audio data onto its own page per the spec + FlushPages(oggStream, outputData, true); + + // ========================================================= + // BODY (Audio Data) + // ========================================================= + var processingState = ProcessingState.Create(info); + + for (int readIndex = 0; readIndex <= floatSamples[0].Length; readIndex += WriteBufferSize) + { + if (readIndex == floatSamples[0].Length) + { + processingState.WriteEndOfStream(); + } + else + { + processingState.WriteData(floatSamples, WriteBufferSize, readIndex); + } + + while (!oggStream.Finished && processingState.PacketOut(out OggPacket packet)) + { + oggStream.PacketIn(packet); + + FlushPages(oggStream, outputData, false); + } + } + + FlushPages(oggStream, outputData, true); + + return outputData.ToArray(); + } + + private static void FlushPages(OggStream oggStream, Stream output, bool force) + { + while (oggStream.PageOut(out OggPage page, force)) + { + output.Write(page.Header, 0, page.Header.Length); + output.Write(page.Body, 0, page.Body.Length); + } + } + + enum PcmSample : int + { + EightBit = 1, + SixteenBit = 2 + } } } diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetTexture.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetTexture.cs index 8c1f233..41ca8cd 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetTexture.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetTexture.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -24,8 +25,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using OpenMetaverse; using OpenMetaverse.Imaging; namespace OpenMetaverse.Assets @@ -41,9 +40,6 @@ namespace OpenMetaverse.Assets /// A object containing image data public ManagedImage Image; - /// - public OpenJPEG.J2KLayerInfo[] LayerInfo; - /// public int Components; @@ -81,7 +77,10 @@ namespace OpenMetaverse.Assets /// public override void Encode() { - AssetData = OpenJPEG.Encode(Image); + using (var writer = new OpenJpegDotNet.IO.Writer(Image.ExportTex2D())) + { + AssetData = writer.Encode(); + } } /// @@ -91,36 +90,27 @@ namespace OpenMetaverse.Assets /// True if the decoding was successful, otherwise false public override bool Decode() { - if (AssetData != null && AssetData.Length > 0) + if (AssetData == null || AssetData.Length <= 0) { return false; } + + this.Components = 0; + + using (var reader = new OpenJpegDotNet.IO.Reader(AssetData)) { - this.Components = 0; - - if (OpenJPEG.DecodeToImage(AssetData, out Image)) - { - if ((Image.Channels & ManagedImage.ImageChannels.Color) != 0) - Components += 3; - if ((Image.Channels & ManagedImage.ImageChannels.Gray) != 0) - ++Components; - if ((Image.Channels & ManagedImage.ImageChannels.Bump) != 0) - ++Components; - if ((Image.Channels & ManagedImage.ImageChannels.Alpha) != 0) - ++Components; - - return true; - } + // *hack: decode from ManagedImage directly or better yet, get rid of ManagedImage entirely! + if (!reader.ReadHeader()) { return false; } + Image = new ManagedImage(reader.DecodeToBitmap()); } - return false; - } + if ((Image.Channels & ManagedImage.ImageChannels.Color) != 0) + Components += 3; + if ((Image.Channels & ManagedImage.ImageChannels.Gray) != 0) + ++Components; + if ((Image.Channels & ManagedImage.ImageChannels.Bump) != 0) + ++Components; + if ((Image.Channels & ManagedImage.ImageChannels.Alpha) != 0) + ++Components; - /// - /// Decodes the begin and end byte positions for each quality layer in - /// the image - /// - /// - public bool DecodeLayerBoundaries() - { - return OpenJPEG.DecodeLayerBoundaries(AssetData, out LayerInfo, out Components); + return true; } } } diff --git a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetWearable.cs b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetWearable.cs index c1f3411..67a8c6e 100644 --- a/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetWearable.cs +++ b/Assets/Plugins/LibreMetaverse/Assets/AssetTypes/AssetWearable.cs @@ -27,7 +27,6 @@ using System; using System.Collections.Generic; using System.Text; -using OpenMetaverse; namespace OpenMetaverse.Assets { diff --git a/Assets/Plugins/LibreMetaverse/AvatarManager.cs b/Assets/Plugins/LibreMetaverse/AvatarManager.cs index 2d59de4..c287647 100644 --- a/Assets/Plugins/LibreMetaverse/AvatarManager.cs +++ b/Assets/Plugins/LibreMetaverse/AvatarManager.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -279,6 +280,29 @@ namespace OpenMetaverse remove { lock (m_AvatarInterestsReplyLock) { m_AvatarInterestsReply -= value; } } } + /// The event subscribers, null of no subscribers + private EventHandler m_AvatarNotesReply; + + ///Raises the AvatarNotesReply Event + /// A AvatarNotesReplyEventArgs object containing + /// the data sent from the simulator + protected virtual void OnAvatarNotesReply(AvatarNotesReplyEventArgs e) + { + EventHandler handler = m_AvatarNotesReply; + handler?.Invoke(this, e); + } + + /// Thread sync lock object + private readonly object m_AvatarNotesReplyLock = new object(); + + /// Raised when the simulator sends us data containing + /// the private notes listed in an agents profile + public event EventHandler AvatarNotesReply + { + add { lock (m_AvatarNotesReplyLock) { m_AvatarNotesReply += value; } } + remove { lock (m_AvatarNotesReplyLock) { m_AvatarNotesReply -= value; } } + } + /// The event subscribers, null of no subscribers private EventHandler m_AvatarPropertiesReply; @@ -561,6 +585,7 @@ namespace OpenMetaverse Client.Network.RegisterCallback(PacketType.AvatarPropertiesReply, AvatarPropertiesHandler); // Client.Network.RegisterCallback(PacketType.AvatarStatisticsReply, AvatarStatisticsHandler); Client.Network.RegisterCallback(PacketType.AvatarInterestsReply, AvatarInterestsHandler); + Client.Network.RegisterCallback(PacketType.AvatarNotesReply, AvatarNotesHandler); // Avatar group callback Client.Network.RegisterCallback(PacketType.AvatarGroupsReply, AvatarGroupsReplyHandler); @@ -709,7 +734,7 @@ namespace OpenMetaverse callback(false, null, null); } }; - cap.BeginGetResponse(null, string.Empty, Client.Settings.CAPS_TIMEOUT); + cap.GetRequestAsync(Client.Settings.CAPS_TIMEOUT); } /// @@ -752,6 +777,36 @@ namespace OpenMetaverse Client.Network.SendPacket(aprp); } + /// + /// Request avatar notes from simulator + /// + /// Target agent UUID + public void RequestAvatarNotes(UUID avatarid) + { + GenericMessagePacket gmp = new GenericMessagePacket + { + AgentData = + { + AgentID = Client.Self.AgentID, + SessionID = Client.Self.SessionID, + TransactionID = UUID.Zero + }, + MethodData = + { + Method = Utils.StringToBytes("avatarnotesrequest"), + Invoice = UUID.Zero + }, + ParamList = new GenericMessagePacket.ParamListBlock[1] + }; + + gmp.ParamList[0] = + new GenericMessagePacket.ParamListBlock + { + Parameter = Utils.StringToBytes(avatarid.ToString()) + }; + Client.Network.SendPacket(gmp); + } + /// /// Start a request for Avatar Picks /// @@ -991,21 +1046,27 @@ namespace OpenMetaverse BornOn = Utils.BytesToString(reply.PropertiesData.BornOn) }; - //properties.CharterMember = Utils.BytesToString(reply.PropertiesData.CharterMember); - uint charter = Utils.BytesToUInt(reply.PropertiesData.CharterMember); - if (charter == 0) + if (reply.PropertiesData.CharterMember.Length == 1) { - properties.CharterMember = "Resident"; + uint charter = Utils.BytesToUInt(reply.PropertiesData.CharterMember); + if (charter == 0) + { + properties.CharterMember = "Resident"; + } + else if (charter == 1) + { + properties.CharterMember = "Trial"; + } + else if (charter == 2) + { + properties.CharterMember = "Charter"; + } + else if (charter == 3) + { + properties.CharterMember = "Employee"; + } } - else if (charter == 2) - { - properties.CharterMember = "Charter"; - } - else if (charter == 3) - { - properties.CharterMember = "Linden"; - } - else + else if (reply.PropertiesData.CharterMember.Length > 1) { properties.CharterMember = Utils.BytesToString(reply.PropertiesData.CharterMember); } @@ -1038,6 +1099,18 @@ namespace OpenMetaverse } } + protected void AvatarNotesHandler(object sender, PacketReceivedEventArgs e) + { + if (m_AvatarNotesReply != null) + { + Packet packet = e.Packet; + AvatarNotesReplyPacket anrp = (AvatarNotesReplyPacket)packet; + string notes = Utils.BytesToString(anrp.Data.Notes); + + OnAvatarNotesReply(new AvatarNotesReplyEventArgs(anrp.Data.TargetID, notes)); + } + } + /// /// EQ Message fired when someone nearby changes their display name /// @@ -1492,6 +1565,22 @@ namespace OpenMetaverse } } + /// Represents the private notes from the profile of an agent + public class AvatarNotesReplyEventArgs : EventArgs + { + /// Get the ID of the agent + public UUID AvatarID { get; } + + /// Get the interests of the agent + public string Notes { get; } + + public AvatarNotesReplyEventArgs(UUID avatarID, string notes) + { + this.AvatarID = avatarID; + this.Notes = notes; + } + } + /// The properties of an agent public class AvatarPropertiesReplyEventArgs : EventArgs { diff --git a/Assets/Plugins/LibreMetaverse/BitPack.cs b/Assets/Plugins/LibreMetaverse/BitPack.cs index 8134a36..65cb070 100644 --- a/Assets/Plugins/LibreMetaverse/BitPack.cs +++ b/Assets/Plugins/LibreMetaverse/BitPack.cs @@ -43,14 +43,14 @@ namespace OpenMetaverse { get { - if (bytePos != 0 && bitPos == 0) + if (bytePos != 0 && BitPos == 0) return bytePos - 1; return bytePos; } } /// - public int BitPos => bitPos; + public int BitPos { get; private set; } private const int MAX_BITS = 8; @@ -58,8 +58,7 @@ namespace OpenMetaverse private static readonly byte[] Off = { 0 }; private int bytePos; - private int bitPos; - + /// /// Default constructor, initialize the bit packer / bit unpacker /// with a byte array and starting position @@ -301,7 +300,7 @@ namespace OpenMetaverse public string UnpackString(int size) { - if (bitPos != 0 || bytePos + size > Data.Length) throw new IndexOutOfRangeException(); + if (BitPos != 0 || bytePos + size > Data.Length) throw new IndexOutOfRangeException(); string str = System.Text.Encoding.UTF8.GetString(Data, bytePos, size); bytePos += size; @@ -310,7 +309,7 @@ namespace OpenMetaverse public UUID UnpackUUID() { - if (bitPos != 0) throw new IndexOutOfRangeException(); + if (BitPos != 0) throw new IndexOutOfRangeException(); UUID val = new UUID(Data, bytePos); bytePos += 16; @@ -338,7 +337,7 @@ namespace OpenMetaverse while (count > 0) { - byte curBit = (byte)(0x80 >> bitPos); + byte curBit = (byte)(0x80 >> BitPos); if ((data[curBytePos] & (0x01 << (count - 1))) != 0) Data[bytePos] |= curBit; @@ -346,12 +345,12 @@ namespace OpenMetaverse Data[bytePos] &= (byte)~curBit; --count; - ++bitPos; + ++BitPos; ++curBitPos; - if (bitPos >= MAX_BITS) + if (BitPos >= MAX_BITS) { - bitPos = 0; + BitPos = 0; ++bytePos; } if (curBitPos >= MAX_BITS) @@ -389,15 +388,15 @@ namespace OpenMetaverse output[curBytePos] <<= 1; // Grab one bit - if ((Data[bytePos] & (0x80 >> bitPos++)) != 0) + if ((Data[bytePos] & (0x80 >> BitPos++)) != 0) ++output[curBytePos]; --count; ++curBitPos; - if (bitPos >= MAX_BITS) + if (BitPos >= MAX_BITS) { - bitPos = 0; + BitPos = 0; ++bytePos; } if (curBitPos >= MAX_BITS) diff --git a/Assets/Plugins/LibreMetaverse/Capabilities/CapsBase.cs b/Assets/Plugins/LibreMetaverse/Capabilities/CapsBase.cs index c297169..efb3ae0 100644 --- a/Assets/Plugins/LibreMetaverse/Capabilities/CapsBase.cs +++ b/Assets/Plugins/LibreMetaverse/Capabilities/CapsBase.cs @@ -1,6 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co - * Copyright (c) 2019, Cinderblocks Design Co. + * Copyright (c) 2019-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -30,12 +30,19 @@ using System.Net; using System.IO; using System.Threading; using System.Security.Cryptography.X509Certificates; -using UnityEngine; namespace OpenMetaverse.Http { public static class CapsBase { + #region http methods + public const string GET = "GET"; + public const string POST = "POST"; + public const string PUT = "PUT"; + public const string DELETE = "DELETE"; + public const string PATCH = "PATCH"; // rfc 5789 + #endregion http methods + public delegate void OpenWriteEventHandler(HttpWebRequest request); public delegate void DownloadProgressEventHandler(HttpWebRequest request, HttpWebResponse response, int bytesReceived, int totalBytesToReceive); public delegate void RequestCompletedEventHandler(HttpWebRequest request, HttpWebResponse response, byte[] responseData, Exception error); @@ -66,7 +73,17 @@ namespace OpenMetaverse.Http } } - public static HttpWebRequest UploadDataAsync(Uri address, X509Certificate2 clientCert, string contentType, byte[] data, + public static HttpWebRequest GetStringAsync(Uri address, X509Certificate2 clientCert, int millisecondsTimeout, + DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback) + { + // Create the request + HttpWebRequest request = SetupRequest(address, clientCert); + request.Method = GET; + DownloadDataAsync(request, millisecondsTimeout, downloadProgressCallback, completedCallback); + return request; + } + + public static HttpWebRequest PostDataAsync(Uri address, X509Certificate2 clientCert, string contentType, byte[] data, int millisecondsTimeout, OpenWriteEventHandler openWriteCallback, DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback) { @@ -75,7 +92,29 @@ namespace OpenMetaverse.Http request.ContentLength = data.Length; if (!string.IsNullOrEmpty(contentType)) request.ContentType = contentType; - request.Method = "POST"; + request.Method = POST; + + // Create an object to hold all of the state for this request + var state = new RequestState(request, data, millisecondsTimeout, openWriteCallback, + downloadProgressCallback, completedCallback); + + // Start the request for a stream to upload to + IAsyncResult result = request.BeginGetRequestStream(OpenWrite, state); + // Register a timeout for the request + ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state, millisecondsTimeout, true); + + return request; + } + public static HttpWebRequest PutDataAsync(Uri address, X509Certificate2 clientCert, string contentType, byte[] data, + int millisecondsTimeout, OpenWriteEventHandler openWriteCallback, DownloadProgressEventHandler downloadProgressCallback, + RequestCompletedEventHandler completedCallback) + { + // Create the request + var request = SetupRequest(address, clientCert); + request.ContentLength = data.Length; + if (!string.IsNullOrEmpty(contentType)) + request.ContentType = contentType; + request.Method = PUT; // Create an object to hold all of the state for this request var state = new RequestState(request, data, millisecondsTimeout, openWriteCallback, @@ -89,13 +128,49 @@ namespace OpenMetaverse.Http return request; } - public static HttpWebRequest DownloadStringAsync(Uri address, X509Certificate2 clientCert, int millisecondsTimeout, - DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback) + public static HttpWebRequest DeleteDataAsync(Uri address, X509Certificate2 clientCert, string contentType, byte[] data, + int millisecondsTimeout, OpenWriteEventHandler openWriteCallback, DownloadProgressEventHandler downloadProgressCallback, + RequestCompletedEventHandler completedCallback) { // Create the request - HttpWebRequest request = SetupRequest(address, clientCert); - request.Method = "GET"; - DownloadDataAsync(request, millisecondsTimeout, downloadProgressCallback, completedCallback); + var request = SetupRequest(address, clientCert); + request.ContentLength = data.Length; + if (!string.IsNullOrEmpty(contentType)) + request.ContentType = contentType; + request.Method = DELETE; + + // Create an object to hold all of the state for this request + var state = new RequestState(request, data, millisecondsTimeout, openWriteCallback, + downloadProgressCallback, completedCallback); + + // Start the request for a stream to upload to + IAsyncResult result = request.BeginGetRequestStream(OpenWrite, state); + // Register a timeout for the request + ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state, millisecondsTimeout, true); + + return request; + } + + public static HttpWebRequest PatchDataAsync(Uri address, X509Certificate2 clientCert, string contentType, byte[] data, + int millisecondsTimeout, OpenWriteEventHandler openWriteCallback, DownloadProgressEventHandler downloadProgressCallback, + RequestCompletedEventHandler completedCallback) + { + // Create the request + var request = SetupRequest(address, clientCert); + request.ContentLength = data.Length; + if (!string.IsNullOrEmpty(contentType)) + request.ContentType = contentType; + request.Method = PATCH; + + // Create an object to hold all of the state for this request + var state = new RequestState(request, data, millisecondsTimeout, openWriteCallback, + downloadProgressCallback, completedCallback); + + // Start the request for a stream to upload to + IAsyncResult result = request.BeginGetRequestStream(OpenWrite, state); + // Register a timeout for the request + ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state, millisecondsTimeout, true); + return request; } @@ -108,7 +183,6 @@ namespace OpenMetaverse.Http // Start the request for the remote server response IAsyncResult result = request.BeginGetResponse(GetResponse, state); - // Register a timeout for the request ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state, millisecondsTimeout, true); } @@ -135,8 +209,8 @@ namespace OpenMetaverse.Http { Logger.Log( string.Format( - "In CapsBase.SetupRequest() setting conn limit for {0}:{1} to {2}", - address.Host, address.Port, Settings.MAX_HTTP_CONNECTIONS), + "In CapsBase.SetupRequest() setting conn limit for {0}:{1} to {2}", + address.Host, address.Port, Settings.MAX_HTTP_CONNECTIONS), Helpers.LogLevel.Debug); request.ServicePoint.ConnectionLimit = Settings.MAX_HTTP_CONNECTIONS; } @@ -205,7 +279,7 @@ namespace OpenMetaverse.Http int totalBytesRead = 0; int totalSize = nolength ? 0 : size; - while ((bytesRead = responseStream.Read(buffer, offset, size)) != 0) + while (responseStream != null && (bytesRead = responseStream.Read(buffer, offset, size)) != 0) { totalBytesRead += bytesRead; diff --git a/Assets/Plugins/LibreMetaverse/Capabilities/CapsClient.cs b/Assets/Plugins/LibreMetaverse/Capabilities/CapsClient.cs index d427e38..faa3c96 100644 --- a/Assets/Plugins/LibreMetaverse/Capabilities/CapsClient.cs +++ b/Assets/Plugins/LibreMetaverse/Capabilities/CapsClient.cs @@ -52,16 +52,33 @@ namespace OpenMetaverse.Http protected OSD _Response; protected AutoResetEvent _ResponseEvent = new AutoResetEvent(false); + #region Constructors + + /// + /// CapsClient Ctor + /// + /// for simulator capability public CapsClient(Uri capability) : this(capability, null, null) { } + /// + /// CapsClient Ctor with name + /// + /// for simulator capability + /// Simulator capability name public CapsClient(Uri capability, string cap_name) : this(capability, cap_name, null) { } + /// + /// CapsClient Ctor with name and certificate + /// + /// for simulator capability + /// Simulator capability name + /// client certificate public CapsClient(Uri capability, string cap_name, X509Certificate2 clientCert) { _Address = capability; @@ -69,37 +86,48 @@ namespace OpenMetaverse.Http _ClientCert = clientCert; } - public void BeginGetResponse(int millisecondsTimeout) - { - BeginGetResponse(null, null, millisecondsTimeout); - } + #endregion Constructors - public void BeginGetResponse(OSD data, OSDFormat format, int millisecondsTimeout) - { - byte[] postData; - string contentType; + #region GET requests - switch (format) + public void GetRequestAsync(int msTimeout) + { + if (_Request != null) { - case OSDFormat.Xml: - postData = OSDParser.SerializeLLSDXmlBytes(data); - contentType = "application/llsd+xml"; - break; - case OSDFormat.Binary: - postData = OSDParser.SerializeLLSDBinary(data); - contentType = "application/llsd+binary"; - break; - case OSDFormat.Json: - default: - postData = System.Text.Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(data)); - contentType = "application/llsd+json"; - break; + _Request.Abort(); + _Request = null; } - - BeginGetResponse(postData, contentType, millisecondsTimeout); + // GET + Logger.DebugLog("[CapsClient] GET " + _Address); + _Request = CapsBase.GetStringAsync(_Address, _ClientCert, msTimeout, DownloadProgressHandler, + RequestCompletedHandler); } - public void BeginGetResponse(byte[] postData, string contentType, int millisecondsTimeout) + public OSD GetRequest(int msTimeout) + { + GetRequestAsync(msTimeout); + _ResponseEvent.WaitOne(msTimeout, false); + return _Response; + } + + #endregion GET requests + + #region POST requests + + public void PostRequestAsync(OSD data, OSDFormat format, int msTimeout) + { + serializeData(data, format, out byte[] serializedData, out string contentType); + PostRequestAsync(serializedData, contentType, msTimeout); + } + + public OSD PostRequest(OSD data, OSDFormat format, int msTimeout) + { + PostRequestAsync(data, format, msTimeout); + _ResponseEvent.WaitOne(msTimeout, false); + return _Response; + } + + public void PostRequestAsync(byte[] postData, string contentType, int msTimeout) { _PostData = postData; _ContentType = contentType; @@ -109,44 +137,143 @@ namespace OpenMetaverse.Http _Request.Abort(); _Request = null; } + // POST + Logger.DebugLog("[CapsClient] POST (" + postData.Length + " bytes) " + _Address); - if (postData == null) + _Request = CapsBase.PostDataAsync(_Address, _ClientCert, contentType, postData, msTimeout, + null, DownloadProgressHandler, RequestCompletedHandler); + } + + public OSD PostRequest(byte[] postData, string contentType, int msTimeout) + { + PostRequestAsync(postData, contentType, msTimeout); + _ResponseEvent.WaitOne(msTimeout, false); + return _Response; + } + + #endregion POST requests + + #region PUT requests + + public void PutRequestAsync(OSD data, OSDFormat format, int msTimeout) + { + serializeData(data, format, out byte[] serializedData, out string contentType); + PutRequestAsync(serializedData, contentType, msTimeout); + } + + public OSD PutRequest(OSD data, OSDFormat format, int msTimeout) + { + PutRequestAsync(data, format, msTimeout); + _ResponseEvent.WaitOne(msTimeout, false); + return _Response; + } + + public void PutRequestAsync(byte[] postData, string contentType, int msTimeout) + { + _PostData = postData; + _ContentType = contentType; + + if (_Request != null) { - // GET - Logger.DebugLog("[CapsClient] GET " + _Address); - _Request = CapsBase.DownloadStringAsync(_Address, _ClientCert, millisecondsTimeout, DownloadProgressHandler, - RequestCompletedHandler); + _Request.Abort(); + _Request = null; } - else + // PUT + Logger.DebugLog("[CapsClient] PUT " + _Address); + + _Request = CapsBase.PutDataAsync(_Address, _ClientCert, contentType, postData, msTimeout, + null, DownloadProgressHandler, RequestCompletedHandler); + } + + public OSD PutRequest(byte[] postData, string contentType, int msTimeout) + { + PutRequestAsync(postData, contentType, msTimeout); + _ResponseEvent.WaitOne(msTimeout, false); + return _Response; + } + + #endregion PUT requests + + #region PATCH requests + + public void PatchRequestAsync(OSD data, OSDFormat format, int msTimeout) + { + serializeData(data, format, out byte[] serializedData, out string contentType); + PatchRequestAsync(serializedData, contentType, msTimeout); + } + + public OSD PatchRequest(OSD data, OSDFormat format, int msTimeout) + { + PatchRequestAsync(data, format, msTimeout); + _ResponseEvent.WaitOne(msTimeout, false); + return _Response; + } + + public void PatchRequestAsync(byte[] postData, string contentType, int msTimeout) + { + _PostData = postData; + _ContentType = contentType; + + if (_Request != null) { - // POST - Logger.DebugLog("[CapsClient] POST (" + postData.Length + " bytes) " + _Address); - _Request = CapsBase.UploadDataAsync(_Address, _ClientCert, contentType, postData, millisecondsTimeout, null, - DownloadProgressHandler, RequestCompletedHandler); + _Request.Abort(); + _Request = null; } + // PATCH + Logger.DebugLog("[CapsClient] PATCH " + _Address); + _Request = CapsBase.PatchDataAsync(_Address, _ClientCert, contentType, postData, msTimeout, + null, DownloadProgressHandler, RequestCompletedHandler); } - public OSD GetResponse(int millisecondsTimeout) + public OSD PatchRequest(byte[] postData, string contentType, int msTimeout) { - BeginGetResponse(millisecondsTimeout); - _ResponseEvent.WaitOne(millisecondsTimeout, false); + PatchRequestAsync(postData, contentType, msTimeout); + _ResponseEvent.WaitOne(msTimeout, false); return _Response; } - public OSD GetResponse(OSD data, OSDFormat format, int millisecondsTimeout) + #endregion PATCH requests + + #region DELETE requests + + public void DeleteRequestAsync(OSD data, OSDFormat format, int msTimeout) { - BeginGetResponse(data, format, millisecondsTimeout); - _ResponseEvent.WaitOne(millisecondsTimeout, false); + serializeData(data, format, out byte[] serializedData, out string contentType); + DeleteRequestAsync(serializedData, contentType, msTimeout); + } + + public OSD DeleteRequest(OSD data, OSDFormat format, int msTimeout) + { + DeleteRequestAsync(data, format, msTimeout); + _ResponseEvent.WaitOne(msTimeout, false); return _Response; } - public OSD GetResponse(byte[] postData, string contentType, int millisecondsTimeout) + public void DeleteRequestAsync(byte[] postData, string contentType, int msTimeout) { - BeginGetResponse(postData, contentType, millisecondsTimeout); - _ResponseEvent.WaitOne(millisecondsTimeout, false); + _PostData = postData; + _ContentType = contentType; + + if (_Request != null) + { + _Request.Abort(); + _Request = null; + } + // DELETE + Logger.DebugLog("[CapsClient] DELETE " + _Address); + _Request = CapsBase.DeleteDataAsync(_Address, _ClientCert, contentType, postData, msTimeout, + null, DownloadProgressHandler, RequestCompletedHandler); + } + + public OSD DeleteRequest(byte[] postData, string contentType, int msTimeout) + { + PatchRequestAsync(postData, contentType, msTimeout); + _ResponseEvent.WaitOne(msTimeout, false); return _Response; } + #endregion DELETE requests + public void Cancel() { _Request?.Abort(); @@ -181,6 +308,33 @@ namespace OpenMetaverse.Http FireCompleteCallback(result, error); } + /// + /// Serializes OSD data for http request + /// + /// formatted data for input + /// Format to serialize data to + /// Output serialized data as byte array + /// content-type string of serialized data + private void serializeData(OSD data, OSDFormat format, out byte[] serializedData, out string contentType) + { + switch (format) + { + case OSDFormat.Xml: + serializedData = OSDParser.SerializeLLSDXmlBytes(data); + contentType = "application/llsd+xml"; + break; + case OSDFormat.Binary: + serializedData = OSDParser.SerializeLLSDBinary(data); + contentType = "application/llsd+binary"; + break; + case OSDFormat.Json: + default: + serializedData = System.Text.Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(data)); + contentType = "application/llsd+json"; + break; + } + } + private void FireCompleteCallback(OSD result, Exception error) { CompleteCallback callback = OnComplete; diff --git a/Assets/Plugins/LibreMetaverse/Capabilities/EventQueueClient.cs b/Assets/Plugins/LibreMetaverse/Capabilities/EventQueueClient.cs index 99e1fb8..154511f 100644 --- a/Assets/Plugins/LibreMetaverse/Capabilities/EventQueueClient.cs +++ b/Assets/Plugins/LibreMetaverse/Capabilities/EventQueueClient.cs @@ -73,7 +73,7 @@ namespace OpenMetaverse.Http byte[] postData = OSDParser.SerializeLLSDXmlBytes(request); - _Request = CapsBase.UploadDataAsync(_Address, null, REQUEST_CONTENT_TYPE, postData, REQUEST_TIMEOUT, OpenWriteHandler, null, RequestCompletedHandler); + _Request = CapsBase.PostDataAsync(_Address, null, REQUEST_CONTENT_TYPE, postData, REQUEST_TIMEOUT, OpenWriteHandler, null, RequestCompletedHandler); } public void Stop(bool immediate) @@ -211,7 +211,7 @@ namespace OpenMetaverse.Http // Resume the connection. The event handler for the connection opening // just sets class _Request variable to the current HttpWebRequest - CapsBase.UploadDataAsync(_Address, null, REQUEST_CONTENT_TYPE, postData, REQUEST_TIMEOUT, + CapsBase.PostDataAsync(_Address, null, REQUEST_CONTENT_TYPE, postData, REQUEST_TIMEOUT, delegate(HttpWebRequest newRequest) { _Request = newRequest; }, null, RequestCompletedHandler); // If the event queue is dead at this point, turn it off since diff --git a/Assets/Plugins/LibreMetaverse/Caps.cs b/Assets/Plugins/LibreMetaverse/Caps.cs index def9b7c..146fa98 100644 --- a/Assets/Plugins/LibreMetaverse/Caps.cs +++ b/Assets/Plugins/LibreMetaverse/Caps.cs @@ -172,9 +172,7 @@ namespace OpenMetaverse "EnvironmentSettings", "EstateChangeInfo", "EventQueueGet", - "FacebookConnect", - "FlickrConnect", - "TwitterConnect", + "ExtEnvironment", "FetchLib2", "FetchLibDescendents2", "FetchInventory2", @@ -193,6 +191,7 @@ namespace OpenMetaverse "IsExperienceAdmin", "IsExperienceContributor", "RegionExperiences", + "ExperienceQuery", "GetMesh", "GetMesh2", "GetMetadata", @@ -221,7 +220,6 @@ namespace OpenMetaverse "RemoteParcelRequest", "RenderMaterials", "RequestTextureDownload", - "RequiredVoiceVersion", "ResourceCostSelected", "RetrieveNavMeshSrc", "SearchStatRequest", @@ -249,6 +247,7 @@ namespace OpenMetaverse "UploadBakedTexture", "UserInfo", "ViewerAsset", + "ViewerBenefits", "ViewerMetrics", "ViewerStartAuction", "ViewerStats", @@ -259,7 +258,7 @@ namespace OpenMetaverse _SeedRequest = new CapsClient(new Uri(_SeedCapsURI), "SeedCaps"); _SeedRequest.OnComplete += SeedRequestCompleteHandler; - _SeedRequest.BeginGetResponse(req, OSDFormat.Xml, Simulator.Client.Settings.CAPS_TIMEOUT); + _SeedRequest.PostRequestAsync(req, OSDFormat.Xml, Simulator.Client.Settings.CAPS_TIMEOUT); } private void SeedRequestCompleteHandler(CapsClient client, OSD result, Exception error) diff --git a/Assets/Plugins/LibreMetaverse/DirectoryManager.cs b/Assets/Plugins/LibreMetaverse/DirectoryManager.cs index 01798d9..fdc964d 100644 --- a/Assets/Plugins/LibreMetaverse/DirectoryManager.cs +++ b/Assets/Plugins/LibreMetaverse/DirectoryManager.cs @@ -355,7 +355,7 @@ namespace OpenMetaverse } /// - /// Parcel information returned from a request + /// Parcel information returned from a request /// /// Represents one of the following: /// A parcel of land on the grid that has its Show In Search flag set @@ -514,8 +514,7 @@ namespace OpenMetaverse protected virtual void OnEventInfo(EventInfoReplyEventArgs e) { EventHandler handler = m_EventInfoReply; - if (handler != null) - handler(this, e); + handler?.Invoke(this, e); } /// Thread sync lock object @@ -537,21 +536,20 @@ namespace OpenMetaverse protected virtual void OnDirEvents(DirEventsReplyEventArgs e) { EventHandler handler = m_DirEvents; - if (handler != null) - handler(this, e); + handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirEventsLock = new object(); - /// Raised when the data server responds to a request. + /// Raised when the data server responds to a request. public event EventHandler DirEventsReply { add { lock (m_DirEventsLock) { m_DirEvents += value; } } remove { lock (m_DirEventsLock) { m_DirEvents -= value; } } } - /// The event subscribers. null if no subcribers + /// The event subscribers. null if no subscribers private EventHandler m_Places; /// Raises the PlacesReply event @@ -560,21 +558,20 @@ namespace OpenMetaverse protected virtual void OnPlaces(PlacesReplyEventArgs e) { EventHandler handler = m_Places; - if (handler != null) - handler(this, e); + handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_PlacesLock = new object(); - /// Raised when the data server responds to a request. + /// Raised when the data server responds to a request. public event EventHandler PlacesReply { add { lock (m_PlacesLock) { m_Places += value; } } remove { lock (m_PlacesLock) { m_Places -= value; } } } - /// The event subscribers. null if no subcribers + /// The event subscribers. null if no subscribers private EventHandler m_DirPlaces; /// Raises the DirPlacesReply event @@ -583,14 +580,13 @@ namespace OpenMetaverse protected virtual void OnDirPlaces(DirPlacesReplyEventArgs e) { EventHandler handler = m_DirPlaces; - if (handler != null) - handler(this, e); + handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirPlacesLock = new object(); - /// Raised when the data server responds to a request. + /// Raised when the data server responds to a request. public event EventHandler DirPlacesReply { add { lock (m_DirPlacesLock) { m_DirPlaces += value; } } @@ -606,14 +602,13 @@ namespace OpenMetaverse protected virtual void OnDirClassifieds(DirClassifiedsReplyEventArgs e) { EventHandler handler = m_DirClassifieds; - if (handler != null) - handler(this, e); + handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirClassifiedsLock = new object(); - /// Raised when the data server responds to a request. + /// Raised when the data server responds to a request. public event EventHandler DirClassifiedsReply { add { lock (m_DirClassifiedsLock) { m_DirClassifieds += value; } } @@ -629,14 +624,13 @@ namespace OpenMetaverse protected virtual void OnDirGroups(DirGroupsReplyEventArgs e) { EventHandler handler = m_DirGroups; - if (handler != null) - handler(this, e); + handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirGroupsLock = new object(); - /// Raised when the data server responds to a request. + /// Raised when the data server responds to a request. public event EventHandler DirGroupsReply { add { lock (m_DirGroupsLock) { m_DirGroups += value; } } @@ -652,14 +646,13 @@ namespace OpenMetaverse protected virtual void OnDirPeople(DirPeopleReplyEventArgs e) { EventHandler handler = m_DirPeople; - if (handler != null) - handler(this, e); + handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirPeopleLock = new object(); - /// Raised when the data server responds to a request. + /// Raised when the data server responds to a request. public event EventHandler DirPeopleReply { add { lock (m_DirPeopleLock) { m_DirPeople += value; } } @@ -675,14 +668,13 @@ namespace OpenMetaverse protected virtual void OnDirLand(DirLandReplyEventArgs e) { EventHandler handler = m_DirLandReply; - if (handler != null) - handler(this, e); + handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirLandLock = new object(); - /// Raised when the data server responds to a request. + /// Raised when the data server responds to a request. public event EventHandler DirLandReply { add { lock (m_DirLandLock) { m_DirLandReply += value; } } @@ -1446,32 +1438,27 @@ namespace OpenMetaverse /// Contains the Event data returned from the data server from an EventInfoRequest public class EventInfoReplyEventArgs : EventArgs { - private readonly DirectoryManager.EventInfo m_MatchedEvent; - /// /// A single EventInfo object containing the details of an event /// - public DirectoryManager.EventInfo MatchedEvent { get { return m_MatchedEvent; } } + public DirectoryManager.EventInfo MatchedEvent { get; } /// Construct a new instance of the EventInfoReplyEventArgs class /// A single EventInfo object containing the details of an event public EventInfoReplyEventArgs(DirectoryManager.EventInfo matchedEvent) { - this.m_MatchedEvent = matchedEvent; + this.MatchedEvent = matchedEvent; } } /// Contains the "Event" detail data returned from the data server public class DirEventsReplyEventArgs : EventArgs { - private readonly UUID m_QueryID; /// The ID returned by - public UUID QueryID { get { return m_QueryID; } } - - private readonly List m_matchedEvents; + public UUID QueryID { get; } /// A list of "Events" returned by the data server - public List MatchedEvents { get { return m_matchedEvents; } } + public List MatchedEvents { get; } /// Construct a new instance of the DirEventsReplyEventArgs class /// The ID of the query returned by the data server. @@ -1479,22 +1466,19 @@ namespace OpenMetaverse /// A list containing the "Events" returned by the search query public DirEventsReplyEventArgs(UUID queryID, List matchedEvents) { - this.m_QueryID = queryID; - this.m_matchedEvents = matchedEvents; + this.QueryID = queryID; + this.MatchedEvents = matchedEvents; } } /// Contains the "Event" list data returned from the data server public class PlacesReplyEventArgs : EventArgs { - private readonly UUID m_QueryID; /// The ID returned by - public UUID QueryID { get { return m_QueryID; } } - - private readonly List m_MatchedPlaces; + public UUID QueryID { get; } /// A list of "Places" returned by the data server - public List MatchedPlaces { get { return m_MatchedPlaces; } } + public List MatchedPlaces { get; } /// Construct a new instance of PlacesReplyEventArgs class /// The ID of the query returned by the data server. @@ -1502,60 +1486,53 @@ namespace OpenMetaverse /// A list containing the "Places" returned by the data server query public PlacesReplyEventArgs(UUID queryID, List matchedPlaces) { - this.m_QueryID = queryID; - this.m_MatchedPlaces = matchedPlaces; + this.QueryID = queryID; + this.MatchedPlaces = matchedPlaces; } } /// Contains the places data returned from the data server public class DirPlacesReplyEventArgs : EventArgs { - private readonly UUID m_QueryID; /// The ID returned by - public UUID QueryID { get { return m_QueryID; } } - - private readonly List m_MatchedParcels; + public UUID QueryID { get; } /// A list containing Places data returned by the data server - public List MatchedParcels { get { return m_MatchedParcels; } } - + public List MatchedParcels { get; } + /// Construct a new instance of the DirPlacesReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list containing land data returned by the data server public DirPlacesReplyEventArgs(UUID queryID, List matchedParcels) { - this.m_QueryID = queryID; - this.m_MatchedParcels = matchedParcels; + this.QueryID = queryID; + this.MatchedParcels = matchedParcels; } } /// Contains the classified data returned from the data server public class DirClassifiedsReplyEventArgs : EventArgs { - private readonly List m_Classifieds; /// A list containing Classified Ads returned by the data server - public List Classifieds { get { return m_Classifieds; } } + public List Classifieds { get; } /// Construct a new instance of the DirClassifiedsReplyEventArgs class /// A list of classified ad data returned from the data server public DirClassifiedsReplyEventArgs(List classifieds) { - this.m_Classifieds = classifieds; + this.Classifieds = classifieds; } } /// Contains the group data returned from the data server public class DirGroupsReplyEventArgs : EventArgs { - private readonly UUID m_QueryID; /// The ID returned by - public UUID QueryID { get { return m_QueryID; } } - - private readonly List m_matchedGroups; + public UUID QueryID { get; } /// A list containing Groups data returned by the data server - public List MatchedGroups { get { return m_matchedGroups; } } + public List MatchedGroups { get; } /// Construct a new instance of the DirGroupsReplyEventArgs class /// The ID of the query returned by the data server. @@ -1563,22 +1540,19 @@ namespace OpenMetaverse /// A list of groups data returned by the data server public DirGroupsReplyEventArgs(UUID queryID, List matchedGroups) { - this.m_QueryID = queryID; - this.m_matchedGroups = matchedGroups; + this.QueryID = queryID; + this.MatchedGroups = matchedGroups; } } /// Contains the people data returned from the data server public class DirPeopleReplyEventArgs : EventArgs { - private readonly UUID m_QueryID; /// The ID returned by - public UUID QueryID { get { return m_QueryID; } } - - private readonly List m_MatchedPeople; + public UUID QueryID { get; } /// A list containing People data returned by the data server - public List MatchedPeople { get { return m_MatchedPeople; } } + public List MatchedPeople { get; } /// Construct a new instance of the DirPeopleReplyEventArgs class /// The ID of the query returned by the data server. @@ -1586,24 +1560,22 @@ namespace OpenMetaverse /// A list of people data returned by the data server public DirPeopleReplyEventArgs(UUID queryID, List matchedPeople) { - this.m_QueryID = queryID; - this.m_MatchedPeople = matchedPeople; + this.QueryID = queryID; + this.MatchedPeople = matchedPeople; } } /// Contains the land sales data returned from the data server public class DirLandReplyEventArgs : EventArgs { - private readonly List m_DirParcels; - /// A list containing land forsale data returned by the data server - public List DirParcels { get { return m_DirParcels; } } + public List DirParcels { get; } /// Construct a new instance of the DirLandReplyEventArgs class /// A list of parcels for sale returned by the data server public DirLandReplyEventArgs(List dirParcels) { - this.m_DirParcels = dirParcels; + this.DirParcels = dirParcels; } } #endregion diff --git a/Assets/Plugins/LibreMetaverse/EstateTools.cs b/Assets/Plugins/LibreMetaverse/EstateTools.cs index 1930e74..a498e8d 100644 --- a/Assets/Plugins/LibreMetaverse/EstateTools.cs +++ b/Assets/Plugins/LibreMetaverse/EstateTools.cs @@ -984,72 +984,66 @@ namespace OpenMetaverse /// Raised on LandStatReply when the report type is for "top colliders" public class TopCollidersReplyEventArgs : EventArgs { - private readonly int m_objectCount; - private readonly Dictionary m_Tasks; - /// /// The number of returned items in LandStatReply /// - public int ObjectCount { get { return m_objectCount; } } + public int ObjectCount { get; } + /// /// A Dictionary of Object UUIDs to tasks returned in LandStatReply /// - public Dictionary Tasks { get { return m_Tasks; } } + public Dictionary Tasks { get; } /// Construct a new instance of the TopCollidersReplyEventArgs class /// The number of returned items in LandStatReply /// Dictionary of Object UUIDs to tasks returned in LandStatReply public TopCollidersReplyEventArgs(int objectCount, Dictionary tasks) { - this.m_objectCount = objectCount; - this.m_Tasks = tasks; + this.ObjectCount = objectCount; + this.Tasks = tasks; } } /// Raised on LandStatReply when the report type is for "top Scripts" public class TopScriptsReplyEventArgs : EventArgs { - private readonly int m_objectCount; - private readonly Dictionary m_Tasks; - /// /// The number of scripts returned in LandStatReply /// - public int ObjectCount { get { return m_objectCount; } } + public int ObjectCount { get; } + /// /// A Dictionary of Object UUIDs to tasks returned in LandStatReply /// - public Dictionary Tasks { get { return m_Tasks; } } + public Dictionary Tasks { get; } /// Construct a new instance of the TopScriptsReplyEventArgs class /// The number of returned items in LandStatReply /// Dictionary of Object UUIDs to tasks returned in LandStatReply public TopScriptsReplyEventArgs(int objectCount, Dictionary tasks) { - this.m_objectCount = objectCount; - this.m_Tasks = tasks; + this.ObjectCount = objectCount; + this.Tasks = tasks; } } /// Returned, along with other info, upon a successful .RequestInfo() public class EstateBansReplyEventArgs : EventArgs { - private readonly uint m_estateID; - private readonly int m_count; - private readonly List m_banned; - /// /// The identifier of the estate /// - public uint EstateID { get { return m_estateID; } } + public uint EstateID { get; } + /// /// The number of returned itmes /// - public int Count { get { return m_count; } } + public int Count { get; } + /// /// List of UUIDs of Banned Users /// - public List Banned { get { return m_banned; } } + public List Banned { get; } /// Construct a new instance of the EstateBansReplyEventArgs class /// The estate's identifier on the grid @@ -1057,31 +1051,29 @@ namespace OpenMetaverse /// User UUIDs banned public EstateBansReplyEventArgs(uint estateID, int count, List banned) { - this.m_estateID = estateID; - this.m_count = count; - this.m_banned = banned; + this.EstateID = estateID; + this.Count = count; + this.Banned = banned; } } /// Returned, along with other info, upon a successful .RequestInfo() public class EstateUsersReplyEventArgs : EventArgs { - private readonly uint m_estateID; - private readonly int m_count; - private readonly List m_allowedUsers; - /// /// The identifier of the estate /// - public uint EstateID { get { return m_estateID; } } + public uint EstateID { get; } + /// /// The number of returned items /// - public int Count { get { return m_count; } } + public int Count { get; } + /// /// List of UUIDs of Allowed Users /// - public List AllowedUsers { get { return m_allowedUsers; } } + public List AllowedUsers { get; } /// Construct a new instance of the EstateUsersReplyEventArgs class /// The estate's identifier on the grid @@ -1089,31 +1081,29 @@ namespace OpenMetaverse /// Allowed users UUIDs public EstateUsersReplyEventArgs(uint estateID, int count, List allowedUsers) { - this.m_estateID = estateID; - this.m_count = count; - this.m_allowedUsers = allowedUsers; + this.EstateID = estateID; + this.Count = count; + this.AllowedUsers = allowedUsers; } } /// Returned, along with other info, upon a successful .RequestInfo() public class EstateGroupsReplyEventArgs : EventArgs { - private readonly uint m_estateID; - private readonly int m_count; - private readonly List m_allowedGroups; - /// /// The identifier of the estate /// - public uint EstateID { get { return m_estateID; } } + public uint EstateID { get; } + /// /// The number of returned items /// - public int Count { get { return m_count; } } + public int Count { get; } + /// /// List of UUIDs of Allowed Groups /// - public List AllowedGroups { get { return m_allowedGroups; } } + public List AllowedGroups { get; } /// Construct a new instance of the EstateGroupsReplyEventArgs class /// The estate's identifier on the grid @@ -1121,31 +1111,29 @@ namespace OpenMetaverse /// Allowed Groups UUIDs public EstateGroupsReplyEventArgs(uint estateID, int count, List allowedGroups) { - this.m_estateID = estateID; - this.m_count = count; - this.m_allowedGroups = allowedGroups; + this.EstateID = estateID; + this.Count = count; + this.AllowedGroups = allowedGroups; } } /// Returned, along with other info, upon a successful .RequestInfo() public class EstateManagersReplyEventArgs : EventArgs { - private readonly uint m_estateID; - private readonly int m_count; - private readonly List m_Managers; - /// /// The identifier of the estate /// - public uint EstateID { get { return m_estateID; } } + public uint EstateID { get; } + /// /// The number of returned items /// - public int Count { get { return m_count; } } + public int Count { get; } + /// /// List of UUIDs of the Estate's Managers /// - public List Managers { get { return m_Managers; } } + public List Managers { get; } /// Construct a new instance of the EstateManagersReplyEventArgs class /// The estate's identifier on the grid @@ -1153,36 +1141,34 @@ namespace OpenMetaverse /// Managers UUIDs public EstateManagersReplyEventArgs(uint estateID, int count, List managers) { - this.m_estateID = estateID; - this.m_count = count; - this.m_Managers = managers; + this.EstateID = estateID; + this.Count = count; + this.Managers = managers; } } /// Returned, along with other info, upon a successful .RequestInfo() public class EstateCovenantReplyEventArgs : EventArgs { - private readonly UUID m_covenantID; - private readonly long m_timestamp; - private readonly string m_estateName; - private readonly UUID m_estateOwnerID; - /// /// The Covenant /// - public UUID CovenantID { get { return m_covenantID; } } + public UUID CovenantID { get; } + /// /// The timestamp /// - public long Timestamp { get { return m_timestamp; } } + public long Timestamp { get; } + /// /// The Estate name /// - public String EstateName { get { return m_estateName; } } + public String EstateName { get; } + /// /// The Estate Owner's ID (can be a GroupID) /// - public UUID EstateOwnerID { get { return m_estateOwnerID; } } + public UUID EstateOwnerID { get; } /// Construct a new instance of the EstateCovenantReplyEventArgs class /// The Covenant ID @@ -1191,10 +1177,10 @@ namespace OpenMetaverse /// The Estate Owner's ID (can be a GroupID) public EstateCovenantReplyEventArgs(UUID covenantID, long timestamp, string estateName, UUID estateOwnerID) { - this.m_covenantID = covenantID; - this.m_timestamp = timestamp; - this.m_estateName = estateName; - this.m_estateOwnerID = estateOwnerID; + this.CovenantID = covenantID; + this.Timestamp = timestamp; + this.EstateName = estateName; + this.EstateOwnerID = estateOwnerID; } } @@ -1203,25 +1189,23 @@ namespace OpenMetaverse /// Returned, along with other info, upon a successful .RequestInfo() public class EstateUpdateInfoReplyEventArgs : EventArgs { - private readonly uint m_estateID; - private readonly bool m_denyNoPaymentInfo; - private readonly string m_estateName; - private readonly UUID m_estateOwner; - /// /// The estate's name /// - public String EstateName { get { return m_estateName; } } + public String EstateName { get; } + /// /// The Estate Owner's ID (can be a GroupID) /// - public UUID EstateOwner { get { return m_estateOwner; } } + public UUID EstateOwner { get; } + /// /// The identifier of the estate on the grid /// - public uint EstateID { get { return m_estateID; } } + public uint EstateID { get; } + /// - public bool DenyNoPaymentInfo { get { return m_denyNoPaymentInfo; } } + public bool DenyNoPaymentInfo { get; } /// Construct a new instance of the EstateUpdateInfoReplyEventArgs class /// The estate's name @@ -1230,10 +1214,10 @@ namespace OpenMetaverse /// public EstateUpdateInfoReplyEventArgs(string estateName, UUID estateOwner, uint estateID, bool denyNoPaymentInfo) { - this.m_estateName = estateName; - this.m_estateOwner = estateOwner; - this.m_estateID = estateID; - this.m_denyNoPaymentInfo = denyNoPaymentInfo; + this.EstateName = estateName; + this.EstateOwner = estateOwner; + this.EstateID = estateID; + this.DenyNoPaymentInfo = denyNoPaymentInfo; } } diff --git a/Assets/Plugins/LibreMetaverse/FriendsManager.cs b/Assets/Plugins/LibreMetaverse/FriendsManager.cs index a6188fb..90c6d14 100644 --- a/Assets/Plugins/LibreMetaverse/FriendsManager.cs +++ b/Assets/Plugins/LibreMetaverse/FriendsManager.cs @@ -25,7 +25,9 @@ using System; using System.Collections.Generic; +using OpenMetaverse.Http; using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; namespace OpenMetaverse { @@ -53,40 +55,25 @@ namespace OpenMetaverse /// public class FriendInfo { - private UUID m_id; - private string m_name; - private bool m_isOnline; private bool m_canSeeMeOnline; private bool m_canSeeMeOnMap; - private bool m_canModifyMyObjects; - private bool m_canSeeThemOnline; - private bool m_canSeeThemOnMap; - private bool m_canModifyTheirObjects; #region Properties /// /// System ID of the avatar /// - public UUID UUID { get { return m_id; } } + public UUID UUID { get; } /// /// full name of the avatar /// - public string Name - { - get { return m_name; } - set { m_name = value; } - } + public string Name { get; set; } /// /// True if the avatar is online /// - public bool IsOnline - { - get { return m_isOnline; } - set { m_isOnline = value; } - } + public bool IsOnline { get; set; } /// /// True if the friend can see if I am online @@ -121,26 +108,22 @@ namespace OpenMetaverse /// /// True if the freind can modify my objects /// - public bool CanModifyMyObjects - { - get { return m_canModifyMyObjects; } - set { m_canModifyMyObjects = value; } - } + public bool CanModifyMyObjects { get; set; } /// /// True if I can see if my friend is online /// - public bool CanSeeThemOnline { get { return m_canSeeThemOnline; } } + public bool CanSeeThemOnline { get; private set; } /// /// True if I can see if my friend is on the map /// - public bool CanSeeThemOnMap { get { return m_canSeeThemOnMap; } } + public bool CanSeeThemOnMap { get; private set; } /// /// True if I can modify my friend's objects /// - public bool CanModifyTheirObjects { get { return m_canModifyTheirObjects; } } + public bool CanModifyTheirObjects { get; private set; } /// /// My friend's rights represented as bitmapped flags @@ -154,7 +137,7 @@ namespace OpenMetaverse results |= FriendRights.CanSeeOnline; if (m_canSeeMeOnMap) results |= FriendRights.CanSeeOnMap; - if (m_canModifyMyObjects) + if (CanModifyMyObjects) results |= FriendRights.CanModifyObjects; return results; @@ -163,7 +146,7 @@ namespace OpenMetaverse { m_canSeeMeOnline = (value & FriendRights.CanSeeOnline) != 0; m_canSeeMeOnMap = (value & FriendRights.CanSeeOnMap) != 0; - m_canModifyMyObjects = (value & FriendRights.CanModifyObjects) != 0; + CanModifyMyObjects = (value & FriendRights.CanModifyObjects) != 0; } } @@ -175,20 +158,20 @@ namespace OpenMetaverse get { FriendRights results = FriendRights.None; - if (m_canSeeThemOnline) + if (CanSeeThemOnline) results |= FriendRights.CanSeeOnline; - if (m_canSeeThemOnMap) + if (CanSeeThemOnMap) results |= FriendRights.CanSeeOnMap; - if (m_canModifyTheirObjects) + if (CanModifyTheirObjects) results |= FriendRights.CanModifyObjects; return results; } set { - m_canSeeThemOnline = (value & FriendRights.CanSeeOnline) != 0; - m_canSeeThemOnMap = (value & FriendRights.CanSeeOnMap) != 0; - m_canModifyTheirObjects = (value & FriendRights.CanModifyObjects) != 0; + CanSeeThemOnline = (value & FriendRights.CanSeeOnline) != 0; + CanSeeThemOnMap = (value & FriendRights.CanSeeOnMap) != 0; + CanModifyTheirObjects = (value & FriendRights.CanModifyObjects) != 0; } } @@ -202,14 +185,14 @@ namespace OpenMetaverse /// Rights you have to see your friend online and to modify their objects internal FriendInfo(UUID id, FriendRights theirRights, FriendRights myRights) { - m_id = id; + UUID = id; m_canSeeMeOnline = (theirRights & FriendRights.CanSeeOnline) != 0; m_canSeeMeOnMap = (theirRights & FriendRights.CanSeeOnMap) != 0; - m_canModifyMyObjects = (theirRights & FriendRights.CanModifyObjects) != 0; + CanModifyMyObjects = (theirRights & FriendRights.CanModifyObjects) != 0; - m_canSeeThemOnline = (myRights & FriendRights.CanSeeOnline) != 0; - m_canSeeThemOnMap = (myRights & FriendRights.CanSeeOnMap) != 0; - m_canModifyTheirObjects = (myRights & FriendRights.CanModifyObjects) != 0; + CanSeeThemOnline = (myRights & FriendRights.CanSeeOnline) != 0; + CanSeeThemOnMap = (myRights & FriendRights.CanSeeOnMap) != 0; + CanModifyTheirObjects = (myRights & FriendRights.CanModifyObjects) != 0; } /// @@ -218,11 +201,11 @@ namespace OpenMetaverse /// A string reprentation of both my rights and my friends rights public override string ToString() { - if (!String.IsNullOrEmpty(m_name)) - return String.Format("{0} (Their Rights: {1}, My Rights: {2})", m_name, TheirFriendRights, + if (!String.IsNullOrEmpty(Name)) + return String.Format("{0} (Their Rights: {1}, My Rights: {2})", Name, TheirFriendRights, MyFriendRights); else - return String.Format("{0} (Their Rights: {1}, My Rights: {2})", m_id, TheirFriendRights, + return String.Format("{0} (Their Rights: {1}, My Rights: {2})", UUID, TheirFriendRights, MyFriendRights); } } @@ -489,7 +472,7 @@ namespace OpenMetaverse /// /// Accept a friendship request /// - /// agentID of avatatar to form friendship with + /// agentID of avatar to form friendship with /// imSessionID of the friendship request message public void AcceptFriendship(UUID fromAgentID, UUID imSessionID) { @@ -517,6 +500,63 @@ namespace OpenMetaverse Client.Avatars.RequestAvatarName(fromAgentID); } + /// + /// Accept friendship request. Only to be used if request was sent via Offline Msg cap + /// This can be determined by the presence of a + /// value in + /// + /// agentID of avatar to form friendship with + public void AcceptFriendshipCapability(UUID fromAgentID) + { + Uri acceptFriendshipCap = Client.Network.CurrentSim.Caps.CapabilityURI("AcceptFriendship"); + if (acceptFriendshipCap == null) + { + Logger.Log("AcceptFriendship capability not found.", Helpers.LogLevel.Warning); + return; + } + UriBuilder builder = new UriBuilder(acceptFriendshipCap) + { + // Second Life has some infintely stupid escaped agent name as part of the uri query. + // Hopefully we don't need it because it makes no goddamn sense at all. Period, but just in case: + // ?from={fromAgentID}&agent_name=\"This%20Sucks\" + Query = $"from={fromAgentID}" + }; + acceptFriendshipCap = builder.Uri; + + var request = new CapsClient(acceptFriendshipCap); + var body = new OSD(); + request.OnComplete += delegate(CapsClient client, OSD result, Exception error) + { + if (error != null) + { + Logger.Log($"AcceptFriendship failed for {fromAgentID}. ({error.Message})", + Helpers.LogLevel.Warning); + return; + } + + if (result != null) + { + if (result is OSDMap resMap + && !(resMap.ContainsKey("success") && resMap["success"].AsBoolean())) { return; } + + FriendInfo friend = new FriendInfo(fromAgentID, FriendRights.CanSeeOnline, FriendRights.CanSeeOnline); + + if (!FriendList.ContainsKey(fromAgentID)) + { + FriendList.Add(friend.UUID, friend); + } + if (FriendRequests.ContainsKey(fromAgentID)) + { + FriendRequests.Remove(fromAgentID); + } + Client.Avatars.RequestAvatarName(fromAgentID); + } + + + }; + request.PostRequestAsync(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + } + /// /// Decline a friendship request /// @@ -534,6 +574,53 @@ namespace OpenMetaverse FriendRequests.Remove(fromAgentID); } + /// + /// Decline friendship request. Only to be used if request was sent via Offline Msg cap + /// This can be determined by the presence of a + /// value in + /// + /// of friend + public void DeclineFriendshipCap(UUID fromAgentID) + { + Uri declineFriendshipCap = Client.Network.CurrentSim.Caps.CapabilityURI("DeclineFriendship"); + if (declineFriendshipCap == null) + { + Logger.Log("DeclineFriendship capability not found.", Helpers.LogLevel.Warning); + return; + } + UriBuilder builder = new UriBuilder(declineFriendshipCap) + { + Query = $"from={fromAgentID}" + }; + declineFriendshipCap = builder.Uri; + + var request = new CapsClient(declineFriendshipCap); + var body = new OSD(); + request.OnComplete += delegate (CapsClient client, OSD result, Exception error) + { + if (error != null) + { + Logger.Log($"AcceptFriendship failed for {fromAgentID}. ({error.Message})", + Helpers.LogLevel.Warning); + return; + } + + if (result != null) + { + if (result is OSDMap resMap && + !(resMap.ContainsKey("success") && resMap["success"].AsBoolean())) { return; } + + if (FriendRequests.ContainsKey(fromAgentID)) + { + FriendRequests.Remove(fromAgentID); + } + } + + + }; + request.DeleteRequestAsync(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + } + /// /// Overload: Offer friendship to an avatar. /// @@ -945,29 +1032,26 @@ namespace OpenMetaverse public class FriendsReadyEventArgs : EventArgs { - private readonly int m_count; - /// Number of friends we have - public int Count { get { return m_count; } } + public int Count { get; } + /// Get the name of the agent we requested a friendship with /// /// Construct a new instance of the FriendsReadyEventArgs class /// - /// The total number of people loaded into the friend list. + /// The total number of people loaded into the friend list. public FriendsReadyEventArgs(int count) { - this.m_count = count; + this.Count = count; } } /// Contains information on a member of our friends list public class FriendInfoEventArgs : EventArgs { - private readonly FriendInfo m_Friend; - /// Get the FriendInfo - public FriendInfo Friend { get { return m_Friend; } } + public FriendInfo Friend { get; } /// /// Construct a new instance of the FriendInfoEventArgs class @@ -975,18 +1059,16 @@ namespace OpenMetaverse /// The FriendInfo public FriendInfoEventArgs(FriendInfo friend) { - this.m_Friend = friend; + this.Friend = friend; } } /// Contains Friend Names public class FriendNamesEventArgs : EventArgs { - private readonly Dictionary m_Names; - /// A dictionary where the Key is the ID of the Agent, /// and the Value is a string containing their name - public Dictionary Names { get { return m_Names; } } + public Dictionary Names { get; } /// /// Construct a new instance of the FriendNamesEventArgs class @@ -995,24 +1077,22 @@ namespace OpenMetaverse /// and the Value is a string containing their name public FriendNamesEventArgs(Dictionary names) { - this.m_Names = names; + this.Names = names; } } /// Sent when another agent requests a friendship with our agent public class FriendshipOfferedEventArgs : EventArgs { - private readonly UUID m_AgentID; - private readonly string m_AgentName; - private readonly UUID m_SessionID; - /// Get the ID of the agent requesting friendship - public UUID AgentID { get { return m_AgentID; } } + public UUID AgentID { get; } + /// Get the name of the agent requesting friendship - public string AgentName { get { return m_AgentName; } } + public string AgentName { get; } + /// Get the ID of the session, used in accepting or declining the /// friendship offer - public UUID SessionID { get { return m_SessionID; } } + public UUID SessionID { get; } /// /// Construct a new instance of the FriendshipOfferedEventArgs class @@ -1023,25 +1103,23 @@ namespace OpenMetaverse /// friendship offer public FriendshipOfferedEventArgs(UUID agentID, string agentName, UUID imSessionID) { - this.m_AgentID = agentID; - this.m_AgentName = agentName; - this.m_SessionID = imSessionID; + this.AgentID = agentID; + this.AgentName = agentName; + this.SessionID = imSessionID; } } /// A response containing the results of our request to form a friendship with another agent public class FriendshipResponseEventArgs : EventArgs { - private readonly UUID m_AgentID; - private readonly string m_AgentName; - private readonly bool m_Accepted; - /// Get the ID of the agent we requested a friendship with - public UUID AgentID { get { return m_AgentID; } } + public UUID AgentID { get; } + /// Get the name of the agent we requested a friendship with - public string AgentName { get { return m_AgentName; } } + public string AgentName { get; } + /// true if the agent accepted our friendship offer - public bool Accepted { get { return m_Accepted; } } + public bool Accepted { get; } /// /// Construct a new instance of the FriendShipResponseEventArgs class @@ -1051,22 +1129,20 @@ namespace OpenMetaverse /// true if the agent accepted our friendship offer public FriendshipResponseEventArgs(UUID agentID, string agentName, bool accepted) { - this.m_AgentID = agentID; - this.m_AgentName = agentName; - this.m_Accepted = accepted; + this.AgentID = agentID; + this.AgentName = agentName; + this.Accepted = accepted; } } /// Contains data sent when a friend terminates a friendship with us public class FriendshipTerminatedEventArgs : EventArgs { - private readonly UUID m_AgentID; - private readonly string m_AgentName; - /// Get the ID of the agent that terminated the friendship with us - public UUID AgentID { get { return m_AgentID; } } + public UUID AgentID { get; } + /// Get the name of the agent that terminated the friendship with us - public string AgentName { get { return m_AgentName; } } + public string AgentName { get; } /// /// Construct a new instance of the FrindshipTerminatedEventArgs class @@ -1075,8 +1151,8 @@ namespace OpenMetaverse /// The name of the friend who terminated the friendship with us public FriendshipTerminatedEventArgs(UUID agentID, string agentName) { - this.m_AgentID = agentID; - this.m_AgentName = agentName; + this.AgentID = agentID; + this.AgentName = agentName; } } @@ -1085,16 +1161,14 @@ namespace OpenMetaverse /// public class FriendFoundReplyEventArgs : EventArgs { - private readonly UUID m_AgentID; - private readonly ulong m_RegionHandle; - private readonly Vector3 m_Location; - /// Get the ID of the agent we have received location information for - public UUID AgentID { get { return m_AgentID; } } + public UUID AgentID { get; } + /// Get the region handle where our mapped friend is located - public ulong RegionHandle { get { return m_RegionHandle; } } + public ulong RegionHandle { get; } + /// Get the simulator local position where our friend is located - public Vector3 Location { get { return m_Location; } } + public Vector3 Location { get; } /// /// Construct a new instance of the FriendFoundReplyEventArgs class @@ -1104,9 +1178,9 @@ namespace OpenMetaverse /// The simulator local position our friend is located public FriendFoundReplyEventArgs(UUID agentID, ulong regionHandle, Vector3 location) { - this.m_AgentID = agentID; - this.m_RegionHandle = regionHandle; - this.m_Location = location; + this.AgentID = agentID; + this.RegionHandle = regionHandle; + this.Location = location; } } #endregion diff --git a/Assets/Plugins/LibreMetaverse/GridClient.cs b/Assets/Plugins/LibreMetaverse/GridClient.cs index 1c1d21e..a6c845d 100644 --- a/Assets/Plugins/LibreMetaverse/GridClient.cs +++ b/Assets/Plugins/LibreMetaverse/GridClient.cs @@ -24,7 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; using LibreMetaverse; namespace OpenMetaverse diff --git a/Assets/Plugins/LibreMetaverse/GridManager.cs b/Assets/Plugins/LibreMetaverse/GridManager.cs index 78e313a..ab87359 100644 --- a/Assets/Plugins/LibreMetaverse/GridManager.cs +++ b/Assets/Plugins/LibreMetaverse/GridManager.cs @@ -382,13 +382,16 @@ namespace OpenMetaverse #endregion Delegates /// Unknown - public float SunPhase { get { return sunPhase; } } - /// Current direction of the sun - public Vector3 SunDirection { get { return sunDirection; } } + public float SunPhase { get; private set; } + + /// Current direction of the sun + public Vector3 SunDirection { get; private set; } + /// Current angular velocity of the sun - public Vector3 SunAngVelocity { get { return sunAngVelocity; } } + public Vector3 SunAngVelocity { get; private set; } + /// Microseconds since the start of SL 4-hour day - public ulong TimeOfDay { get { return timeOfDay; } } + public ulong TimeOfDay { get; private set; } /// A dictionary of all the regions, indexed by region name internal Dictionary Regions = new Dictionary(); @@ -396,10 +399,6 @@ namespace OpenMetaverse internal Dictionary RegionsByHandle = new Dictionary(); private GridClient Client; - private float sunPhase; - private Vector3 sunDirection; - private Vector3 sunAngVelocity; - private ulong timeOfDay; /// /// Constructor @@ -430,7 +429,7 @@ namespace OpenMetaverse OSDMap body = new OSDMap {["Flags"] = OSD.FromInteger((int) layer)}; request.OnComplete += new CapsClient.CompleteCallback(MapLayerResponseHandler); - request.BeginGetResponse(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } } @@ -624,9 +623,9 @@ namespace OpenMetaverse if (m_GridLayer != null) { - for (int i = 0; i < layerData.Count; i++) + foreach (var data in layerData) { - OSDMap thisLayerData = (OSDMap)layerData[i]; + OSDMap thisLayerData = (OSDMap)data; GridLayer layer; layer.Bottom = thisLayerData["Bottom"].AsInteger(); @@ -635,7 +634,7 @@ namespace OpenMetaverse layer.Right = thisLayerData["Right"].AsInteger(); layer.ImageID = thisLayerData["ImageID"].AsUUID(); - OnGridLayer(new GridLayerEventArgs(layer)); + OnGridLayer(new GridLayerEventArgs(layer)); } } @@ -695,18 +694,18 @@ namespace OpenMetaverse GridItemType type = (GridItemType)reply.RequestData.ItemType; List items = new List(); - for (int i = 0; i < reply.Data.Length; i++) + foreach (var data in reply.Data) { - string name = Utils.BytesToString(reply.Data[i].Name); + string name = Utils.BytesToString(data.Name); switch (type) { case GridItemType.AgentLocations: MapAgentLocation location = new MapAgentLocation(); - location.GlobalX = reply.Data[i].X; - location.GlobalY = reply.Data[i].Y; + location.GlobalX = data.X; + location.GlobalY = data.Y; location.Identifier = name; - location.AvatarCount = reply.Data[i].Extra; + location.AvatarCount = data.Extra; items.Add(location); break; case GridItemType.Classified: @@ -715,28 +714,28 @@ namespace OpenMetaverse break; case GridItemType.LandForSale: MapLandForSale landsale = new MapLandForSale(); - landsale.GlobalX = reply.Data[i].X; - landsale.GlobalY = reply.Data[i].Y; - landsale.ID = reply.Data[i].ID; + landsale.GlobalX = data.X; + landsale.GlobalY = data.Y; + landsale.ID = data.ID; landsale.Name = name; - landsale.Size = reply.Data[i].Extra; - landsale.Price = reply.Data[i].Extra2; + landsale.Size = data.Extra; + landsale.Price = data.Extra2; items.Add(landsale); break; case GridItemType.MatureEvent: MapMatureEvent matureEvent = new MapMatureEvent(); - matureEvent.GlobalX = reply.Data[i].X; - matureEvent.GlobalY = reply.Data[i].Y; + matureEvent.GlobalX = data.X; + matureEvent.GlobalY = data.Y; matureEvent.Description = name; - matureEvent.Flags = (DirectoryManager.EventFlags)reply.Data[i].Extra2; + matureEvent.Flags = (DirectoryManager.EventFlags)data.Extra2; items.Add(matureEvent); break; case GridItemType.PgEvent: MapPGEvent PGEvent = new MapPGEvent(); - PGEvent.GlobalX = reply.Data[i].X; - PGEvent.GlobalY = reply.Data[i].Y; + PGEvent.GlobalX = data.X; + PGEvent.GlobalY = data.Y; PGEvent.Description = name; - PGEvent.Flags = (DirectoryManager.EventFlags)reply.Data[i].Extra2; + PGEvent.Flags = (DirectoryManager.EventFlags)data.Extra2; items.Add(PGEvent); break; case GridItemType.Popular: @@ -745,26 +744,26 @@ namespace OpenMetaverse break; case GridItemType.Telehub: MapTelehub teleHubItem = new MapTelehub(); - teleHubItem.GlobalX = reply.Data[i].X; - teleHubItem.GlobalY = reply.Data[i].Y; + teleHubItem.GlobalX = data.X; + teleHubItem.GlobalY = data.Y; items.Add(teleHubItem); break; case GridItemType.AdultLandForSale: MapAdultLandForSale adultLandsale = new MapAdultLandForSale(); - adultLandsale.GlobalX = reply.Data[i].X; - adultLandsale.GlobalY = reply.Data[i].Y; - adultLandsale.ID = reply.Data[i].ID; + adultLandsale.GlobalX = data.X; + adultLandsale.GlobalY = data.Y; + adultLandsale.ID = data.ID; adultLandsale.Name = name; - adultLandsale.Size = reply.Data[i].Extra; - adultLandsale.Price = reply.Data[i].Extra2; + adultLandsale.Size = data.Extra; + adultLandsale.Price = data.Extra2; items.Add(adultLandsale); break; case GridItemType.AdultEvent: MapAdultEvent adultEvent = new MapAdultEvent(); - adultEvent.GlobalX = reply.Data[i].X; - adultEvent.GlobalY = reply.Data[i].Y; - adultEvent.Description = Utils.BytesToString(reply.Data[i].Name); - adultEvent.Flags = (DirectoryManager.EventFlags)reply.Data[i].Extra2; + adultEvent.GlobalX = data.X; + adultEvent.GlobalY = data.Y; + adultEvent.Description = Utils.BytesToString(data.Name); + adultEvent.Flags = (DirectoryManager.EventFlags)data.Extra2; items.Add(adultEvent); break; default: @@ -784,10 +783,10 @@ namespace OpenMetaverse { SimulatorViewerTimeMessagePacket time = (SimulatorViewerTimeMessagePacket)e.Packet; - sunPhase = time.TimeInfo.SunPhase; - sunDirection = time.TimeInfo.SunDirection; - sunAngVelocity = time.TimeInfo.SunAngVelocity; - timeOfDay = time.TimeInfo.UsecSinceStart; + SunPhase = time.TimeInfo.SunPhase; + SunDirection = time.TimeInfo.SunDirection; + SunAngVelocity = time.TimeInfo.SunAngVelocity; + TimeOfDay = time.TimeInfo.UsecSinceStart; // TODO: Does anyone have a use for the time stuff? } @@ -857,72 +856,59 @@ namespace OpenMetaverse public class CoarseLocationUpdateEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly List m_NewEntries; - private readonly List m_RemovedEntries; - - public Simulator Simulator { get { return m_Simulator; } } - public List NewEntries { get { return m_NewEntries; } } - public List RemovedEntries { get { return m_RemovedEntries; } } + public Simulator Simulator { get; } + public List NewEntries { get; } + public List RemovedEntries { get; } public CoarseLocationUpdateEventArgs(Simulator simulator, List newEntries, List removedEntries) { - this.m_Simulator = simulator; - this.m_NewEntries = newEntries; - this.m_RemovedEntries = removedEntries; + this.Simulator = simulator; + this.NewEntries = newEntries; + this.RemovedEntries = removedEntries; } } public class GridRegionEventArgs : EventArgs { - private readonly GridRegion m_Region; - public GridRegion Region { get { return m_Region; } } + public GridRegion Region { get; } public GridRegionEventArgs(GridRegion region) { - this.m_Region = region; + this.Region = region; } } public class GridLayerEventArgs : EventArgs { - private readonly GridLayer m_Layer; - - public GridLayer Layer { get { return m_Layer; } } + public GridLayer Layer { get; } public GridLayerEventArgs(GridLayer layer) { - this.m_Layer = layer; + this.Layer = layer; } } public class GridItemsEventArgs : EventArgs { - private readonly GridItemType m_Type; - private readonly List m_Items; - - public GridItemType Type { get { return m_Type; } } - public List Items { get { return m_Items; } } + public GridItemType Type { get; } + public List Items { get; } public GridItemsEventArgs(GridItemType type, List items) { - this.m_Type = type; - this.m_Items = items; + this.Type = type; + this.Items = items; } } public class RegionHandleReplyEventArgs : EventArgs { - private readonly UUID m_RegionID; - private readonly ulong m_RegionHandle; - - public UUID RegionID { get { return m_RegionID; } } - public ulong RegionHandle { get { return m_RegionHandle; } } + public UUID RegionID { get; } + public ulong RegionHandle { get; } public RegionHandleReplyEventArgs(UUID regionID, ulong regionHandle) { - this.m_RegionID = regionID; - this.m_RegionHandle = regionHandle; + this.RegionID = regionID; + this.RegionHandle = regionHandle; } } diff --git a/Assets/Plugins/LibreMetaverse/GroupManager.cs b/Assets/Plugins/LibreMetaverse/GroupManager.cs index 6a9ef66..63c479d 100644 --- a/Assets/Plugins/LibreMetaverse/GroupManager.cs +++ b/Assets/Plugins/LibreMetaverse/GroupManager.cs @@ -1058,7 +1058,7 @@ namespace OpenMetaverse }; OSDMap requestData = new OSDMap(1) {["group_id"] = @group}; - req.BeginGetResponse(requestData, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT * 4); + req.PostRequestAsync(requestData, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT * 4); return requestID; } @@ -1696,7 +1696,7 @@ namespace OpenMetaverse }; - req.BeginGetResponse(Client.Settings.CAPS_TIMEOUT); + req.GetRequestAsync(Client.Settings.CAPS_TIMEOUT); } /// @@ -1737,7 +1737,7 @@ namespace OpenMetaverse } OSDRequest["ban_ids"] = banIDs; - req.BeginGetResponse(OSDRequest, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + req.PostRequestAsync(OSDRequest, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } diff --git a/Assets/Plugins/LibreMetaverse/Imaging/BakeLayer.cs b/Assets/Plugins/LibreMetaverse/Imaging/BakeLayer.cs index decd339..8d33ab2 100644 --- a/Assets/Plugins/LibreMetaverse/Imaging/BakeLayer.cs +++ b/Assets/Plugins/LibreMetaverse/Imaging/BakeLayer.cs @@ -25,7 +25,6 @@ */ using System; -using System.Reflection; using System.Collections.Generic; using System.IO; //using System.Drawing; @@ -161,9 +160,16 @@ namespace OpenMetaverse.Imaging if (bakeType == BakeType.Head) { - DrawLayer(LoadResourceLayer("head_color.tga"), false); - AddAlpha(bakedTexture.Image, LoadResourceLayer("head_alpha.tga")); - MultiplyLayerFromAlpha(bakedTexture.Image, LoadResourceLayer("head_skingrain.tga")); + if (DrawLayer(LoadResourceLayer("head_color.tga"), false) == true) + { + AddAlpha(bakedTexture.Image, LoadResourceLayer("head_alpha.tga")); + MultiplyLayerFromAlpha(bakedTexture.Image, LoadResourceLayer("head_skingrain.tga")); + Logger.Log("[Bake]: created head master bake", Helpers.LogLevel.Debug); + } + else + { + Logger.Log("[Bake]: Unable to draw layer from texture file", Helpers.LogLevel.Debug); + } } if (skinTexture.Texture == null) @@ -424,8 +430,8 @@ namespace OpenMetaverse.Imaging private bool MaskBelongsToBake(string mask) { - return (bakeType != BakeType.LowerBody || !mask.Contains("upper")) - && (bakeType != BakeType.LowerBody || !mask.Contains("shirt")) + return (bakeType != BakeType.LowerBody || !mask.Contains("upper")) + && (bakeType != BakeType.LowerBody || !mask.Contains("shirt")) && (bakeType != BakeType.UpperBody || !mask.Contains("lower")); } @@ -460,16 +466,25 @@ namespace OpenMetaverse.Imaging byte[] sourceAlpha = sourceHasAlpha ? source.Alpha : null; byte[] sourceBump = sourceHasBump ? source.Bump : null; + /* 4c506093003675ce6f24d7273812c4440adc87b7 + Update BakeLayer.cs + add unneeded but nice debug info around head_color loading + correct color loading due to alpha being broken + */ + bool loadedAlpha = false; for (int y = 0; y < bakeHeight; y++) { for (int x = 0; x < bakeWidth; x++) { + loadedAlpha = false; alpha = 0; alphaInv = 0; + if (sourceHasAlpha) { if (sourceAlpha.Length > i) { + loadedAlpha = true; alpha = sourceAlpha[i]; alphaInv = (byte)(Byte.MaxValue - alpha); } @@ -481,9 +496,18 @@ namespace OpenMetaverse.Imaging { if ((sourceRed.Length > i) && (sourceGreen.Length > i) && (sourceBlue.Length > i)) { - bakedRed[i] = (byte)((bakedRed[i] * alphaInv + sourceRed[i] * alpha) >> 8); - bakedGreen[i] = (byte)((bakedGreen[i] * alphaInv + sourceGreen[i] * alpha) >> 8); - bakedBlue[i] = (byte)((bakedBlue[i] * alphaInv + sourceBlue[i] * alpha) >> 8); + if (loadedAlpha == true) + { + bakedRed[i] = (byte)((bakedRed[i] * alphaInv + sourceRed[i] * alpha) >> 8); + bakedGreen[i] = (byte)((bakedGreen[i] * alphaInv + sourceGreen[i] * alpha) >> 8); + bakedBlue[i] = (byte)((bakedBlue[i] * alphaInv + sourceBlue[i] * alpha) >> 8); + } + else + { + bakedRed[i] = sourceRed[i]; + bakedGreen[i] = sourceGreen[i]; + bakedBlue[i] = sourceBlue[i]; + } } } } @@ -519,7 +543,7 @@ namespace OpenMetaverse.Imaging /// /// Destination image /// Source image - /// Sanitization was succefull + /// Sanitization was successful private bool SanitizeLayers(ManagedImage dest, ManagedImage src) { if (dest == null || src == null) return false; diff --git a/Assets/Plugins/LibreMetaverse/Imaging/ManagedImage.cs b/Assets/Plugins/LibreMetaverse/Imaging/ManagedImage.cs index 0c7b7d4..a6c1cac 100644 --- a/Assets/Plugins/LibreMetaverse/Imaging/ManagedImage.cs +++ b/Assets/Plugins/LibreMetaverse/Imaging/ManagedImage.cs @@ -144,24 +144,24 @@ namespace OpenMetaverse.Imaging Alpha = new byte[pixelCount]; //BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, Width, Height), - // ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); - - //unsafe - //{ - // byte* pixel = (byte*)bd.Scan0; - - // for (int i = 0; i < pixelCount; i++) - // { - // // GDI+ gives us BGRA and we need to turn that in to RGBA - // Blue[i] = *(pixel++); - // Green[i] = *(pixel++); - // Red[i] = *(pixel++); - // Alpha[i] = *(pixel++); - // } - //} - - //bitmap.UnlockBits(bd); + // ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + /* + unsafe + { + byte* pixel = (byte*)bd.Scan0; + for (int i = 0; i < pixelCount; i++) + { + // GDI+ gives us BGRA and we need to turn that in to RGBA + Blue[i] = *(pixel++); + Green[i] = *(pixel++); + Red[i] = *(pixel++); + Alpha[i] = *(pixel++); + } + } + + */ + for (int i = 0; i < pixelCount; i++) { Color32 bit = tex.GetPixel(i%Width , i / Width); @@ -171,7 +171,10 @@ namespace OpenMetaverse.Imaging Alpha[i] = bit.a; } + //bitmap.UnlockBits(bd); } + // this 16bit grayscale image format is commented-out for Raindrop and we need to investigate what its used for; can we just ignore it? + // unity does not seem to support 16bit grayscale //else if (tex.format == TextureFormat.Alpha8) // PixelFormat.Format16bppGrayScale --- 16 bits per pixel. The color information specifies 65536 shades of gray. //{ // Channels = ImageChannels.Gray; @@ -215,6 +218,7 @@ namespace OpenMetaverse.Imaging } } + // can we remove the following? this Format32bppRgb is a ridiculous format. else if (tex.format == TextureFormat.RGB24) // PixelFormat.Format32bppRgb) --- The remaining 8 bits are not used. { Channels = ImageChannels.Color; diff --git a/Assets/Plugins/LibreMetaverse/Imaging/TGALoader.cs b/Assets/Plugins/LibreMetaverse/Imaging/TGALoader.cs index 98f2c67..31d6e0a 100644 --- a/Assets/Plugins/LibreMetaverse/Imaging/TGALoader.cs +++ b/Assets/Plugins/LibreMetaverse/Imaging/TGALoader.cs @@ -1,28 +1,28 @@ -///* -// * 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. -// */ +/* + * 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.IO; @@ -130,192 +130,182 @@ namespace OpenMetaverse.Imaging } } - public static bool isRLE(string path) + struct tgaCD { - return true; + public uint RMask, GMask, BMask, AMask; + public byte RShift, GShift, BShift, AShift; + public uint FinalOr; + public bool NeedNoConvert; } - public static Texture2D LoadTGAOld(System.IO.Stream source) + static uint UnpackColor( + uint sourceColor, ref tgaCD cd) { - byte[] buffer = new byte[source.Length]; - source.Read(buffer, 0, buffer.Length); - - System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer); - - using (System.IO.BinaryReader br = new System.IO.BinaryReader(ms)) + if (cd.RMask == 0xFF && cd.GMask == 0xFF && cd.BMask == 0xFF) { - tgaHeader header = new tgaHeader(); - header.Read(br); - - if (header.ImageSpec.PixelDepth != 8 && - header.ImageSpec.PixelDepth != 16 && - header.ImageSpec.PixelDepth != 24 && - header.ImageSpec.PixelDepth != 32) - throw new ArgumentException("Not a supported tga file."); - - //if (header.ImageSpec.PixelDepth == 8) - // throw new ArgumentException("TGA texture had 8 bit depth."); - //if (header.ImageSpec.PixelDepth == 16) - // throw new ArgumentException("TGA texture had 16 bit depth."); - - if (header.ImageSpec.AlphaBits > 8) - throw new ArgumentException("Not a supported tga file: too many Alpha bits"); - - if (header.ImageSpec.Width > 4096 || - header.ImageSpec.Height > 4096) - throw new ArgumentException("Image too large."); - - - long len = br.BaseStream.Length; - //if (header.ImageSpec.PixelDepth == 24) - //{ - // long expectedBytes = header.ImageSpec.Width * header.ImageSpec.Height * 3; - // if (len < expectedBytes) - // { - // //throw new ArgumentException("TGA texture has 24 bit depth, height and width of " +header.ImageSpec.Width+ " " + header.ImageSpec.Height+ " but the number of bytes in file is " + len); - // throw new ArgumentException("the 24bit TGA file is smaller than expected"); - // } - - //} - //if (header.ImageSpec.PixelDepth == 32) - //{ - // long expectedBytes = header.ImageSpec.Width * header.ImageSpec.Height * 4; - // if (len < expectedBytes) - // { - // //throw new ArgumentException("TGA texture has 32 bit depth, height and width of " +header.ImageSpec.Width+ " " + header.ImageSpec.Height+ " but the number of bytes in file is " + len); - // throw new ArgumentException("the 32bit TGA file is smaller than expected"); - // } - - //} - //if (header.ImageSpec.PixelDepth == 8) - //{ - // long expectedBytes = header.ImageSpec.Width * header.ImageSpec.Height * 1; - // if (len < expectedBytes) - // { - // //throw new ArgumentException("TGA texture has 32 bit depth, height and width of " +header.ImageSpec.Width+ " " + header.ImageSpec.Height+ " but the number of bytes in file is " + len); - // throw new ArgumentException("the 8bit TGA file is smaller than expected"); - // } - - //} - - - Texture2D b; - int width = header.ImageSpec.Width; - int height = header.ImageSpec.Height; - Color32[] pulledColors = new Color32[width * height]; - //BitmapData bd; - - //NOTE NON COMPRESSED textures only!!! - - // Create a bitmap for the image. - // Only include an alpha layer when the image requires one. - if (header.ImageSpec.AlphaBits > 0 || - header.ImageSpec.PixelDepth == 8 || // Assume 8 bit images are alpha only - header.ImageSpec.PixelDepth == 32) // Assume 32 bit images are ARGB - { // Image needs an alpha layer - b = new Texture2D( - header.ImageSpec.Width, - header.ImageSpec.Height, - TextureFormat.ARGB32, - false - /*PixelFormat.Format32bppArgb*/); - - //bd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), - // ImageLockMode.WriteOnly, - // PixelFormat.Format32bppPArgb); - - //Debug.Log("Debug the color: " + pulledColors[5]); //ok we know these are correct. - //Debug.Log("Debug the color: " + pulledColors[500]); - //Debug.Log("Debug the color: " + pulledColors[50000]); - } - else - { // Image does not need an alpha layer, so do not include one. - b = new Texture2D( - header.ImageSpec.Width, - header.ImageSpec.Height, - TextureFormat.RGB24, - false - /*PixelFormat.Format32bppRgb*/); - - //bd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), - // ImageLockMode.WriteOnly, - // PixelFormat.Format32bppRgb); - - } - - if (header.ImageSpec.PixelDepth == 8) - { - for (int i = 0; i < width * height; i++) - { - byte red = br.ReadByte(); - - pulledColors[i] = new Color32(0, 0, 0, red); - } - - } else if (header.ImageSpec.PixelDepth == 16) - { - throw new ArgumentException("TGA texture had 16 bit depth."); - - } else if (header.ImageSpec.PixelDepth == 24) //TODO: handle the case of 'alpha channel is present'! - { - for (int i = 0; i < width * height; i++) - { - byte red = br.ReadByte(); - byte green = br.ReadByte(); - byte blue = br.ReadByte(); - - pulledColors[i] = new Color32(blue, green, red, 1); - } - - } else if (header.ImageSpec.PixelDepth == 32) - { - - for (int i = 0; i < width * height; i++) - { - byte red = br.ReadByte(); - byte green = br.ReadByte(); - byte blue = br.ReadByte(); - byte alpha = br.ReadByte(); - - pulledColors[i] = new Color32(blue, green, red, alpha); - } - } - - //switch (header.ImageSpec.PixelDepth) - //{ - // case 8: - // decodeStandard8(bd, header, br); - // break; - // case 16: - // if (header.ImageSpec.AlphaBits > 0) - // decodeSpecial16(bd, header, br); - // else - // decodeStandard16(bd, header, br); - // break; - // case 24: - // if (header.ImageSpec.AlphaBits > 0) - // decodeSpecial24(bd, header, br); - // else - // decodeStandard24(bd, header, br); - // break; - // case 32: - // decodeStandard32(bd, header, br); - // break; - // default: - // b.UnlockBits(bd); - // b.Dispose(); - // return null; - //} - - //b.UnlockBits(bd); - b.SetPixels32(pulledColors); - b.Apply(); - - return b; + // Special case to deal with 8-bit TGA files that we treat as alpha masks + return sourceColor << 24; } + else + { + uint rpermute = (sourceColor << cd.RShift) | (sourceColor >> (32 - cd.RShift)); + uint gpermute = (sourceColor << cd.GShift) | (sourceColor >> (32 - cd.GShift)); + uint bpermute = (sourceColor << cd.BShift) | (sourceColor >> (32 - cd.BShift)); + uint apermute = (sourceColor << cd.AShift) | (sourceColor >> (32 - cd.AShift)); + uint result = + (rpermute & cd.RMask) | (gpermute & cd.GMask) + | (bpermute & cd.BMask) | (apermute & cd.AMask) | cd.FinalOr; + return result; + } } + + static unsafe void decodeLine( + Color32[] b, int texWidth, int texHeight, + int line, + int byp, + byte[] data, + ref tgaCD cd) + { + if (cd.NeedNoConvert) + { + // fast copy + uint offset_colorArray_scanline = (uint)(line * texWidth + 0); //should be large enough? + //uint* linep = (uint*)((byte*)b.Scan0.ToPointer() + line * b.Stride); + fixed (byte* ptr = data) + { + uint* sptr = (uint*)ptr; + for (int i = 0; i < texWidth; ++i) + { + b[i + offset_colorArray_scanline] = makeColor32fromUInt(sptr[i]); + } + } + } + else + { + //byte* linep = (byte*)b.Scan0.ToPointer() + line * b.Stride; + uint offset_colorArray_scanline = (uint)(line * texWidth + 0); + + //uint* up = (uint*)linep; + + int rdi = 0; + + fixed (byte* ptr = data) + { + for (int i = 0; i < texWidth; ++i) + { + uint x = 0; + for (int j = 0; j < byp; ++j) + { + x |= ((uint)ptr[rdi]) << (j << 3); //load all bytes that represent the pixel's color into register x. + ++rdi; + } + uint unpackedColoByte = UnpackColor(x, ref cd); + b[i+line*texWidth] = makeColor32fromUInt(unpackedColoByte); + } + } + } + } + + + static void decodeRle( + Color32[] b, int texWidth, int texHeight, + int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp) + { + try + { + int w = texWidth; + // make buffer larger, so in case of emergency I can decode + // over line ends. + byte[] linebuffer = new byte[(w + 128) * byp]; + int maxindex = w * byp; + int index = 0; + + for (int j = 0; j < texHeight; ++j) + { + while (index < maxindex) + { + byte blocktype = br.ReadByte(); //MSB of blocktype: 1 means Raw packet(non-RLE), 0 means RLE packet + + int bytestoread; + int bytestocopy; + + if (blocktype >= 0x80) // run-length packet. - read 1 color and replicate them by bytestocopy + { + int pixel_count_minus_1 = (blocktype - 0x80); + bytestoread = byp; // pixel data + bytestocopy = byp * pixel_count_minus_1; + } + else //raw packet (non run-lenght encoding.) + { + bytestoread = byp * (blocktype + 1); + bytestocopy = 0; + } + + //if (index + bytestoread > maxindex) + // throw new System.ArgumentException ("Corrupt TGA"); + + br.Read(linebuffer, index, bytestoread); + index += bytestoread; + + for (int i = 0; i != bytestocopy; ++i) + { + linebuffer[index + i] = linebuffer[index + i - bytestoread]; + } + index += bytestocopy; + } + // todo: did i wrongly flip the orientation here? is the original actually correct? + if (!bottomUp) + decodeLine(b, texWidth, texHeight, j, byp, linebuffer, ref cd); + else + decodeLine(b, texWidth, texHeight, texHeight - j - 1, byp, linebuffer, ref cd); + + if (index > maxindex) + { + Array.Copy(linebuffer, maxindex, linebuffer, 0, index - maxindex); + index -= maxindex; + } + else + index = 0; + + } + } + catch (System.IO.EndOfStreamException) + { + } + } + + + + /// + /// decodes the non-RLE version of tga files. + /// + /// output reference to texture/bmp + /// bytes-per-pixel + /// ??? + /// binary reader that is reading the data file + /// if the image sh. + static void decodePlain( + Color32[] b, int texWidth, int texHeight, + int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp) + { + int w = texWidth; + byte[] linebuffer = new byte[w * byp]; + + for (int j = 0; j < texHeight; ++j) + { + br.Read(linebuffer, 0, w * byp); + + // todo: did i wrongly flip the orientation here? is the original actually correct? + if (!bottomUp) + decodeLine(b, texWidth, texHeight, j, byp, linebuffer, ref cd); + else + decodeLine(b, texWidth, texHeight, texHeight - j - 1, byp, linebuffer, ref cd); + } + } + + static void decodeStandard8( Color32[] b, int texWidth, int texHeight, tgaHeader hdr, @@ -621,107 +611,6 @@ namespace OpenMetaverse.Imaging - struct tgaCD - { - public uint RMask, GMask, BMask, AMask; - public byte RShift, GShift, BShift, AShift; - public uint FinalOr; - public bool NeedNoConvert; - } - - - /// - /// decodes the non-RLE version of tga files. - /// - /// output reference to texture/bmp - /// bytes-per-pixel - /// ??? - /// binary reader that is reading the data file - /// if the image sh. - static void decodePlain( - Color32[] b, int texWidth, int texHeight, - int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp) - { - int w = texWidth; - byte[] linebuffer = new byte[w * byp]; - - for (int j = 0; j < texHeight; ++j) - { - br.Read(linebuffer, 0, w * byp); - - if (!bottomUp) - decodeLine(b, texWidth, texHeight, j, byp, linebuffer, ref cd); - else - decodeLine(b, texWidth, texHeight, texHeight - j - 1, byp, linebuffer, ref cd); - } - } - - static void decodeRle( - Color32[] b, int texWidth, int texHeight, - int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp) - { - try - { - int w = texWidth; - // make buffer larger, so in case of emergency I can decode - // over line ends. - byte[] linebuffer = new byte[(w + 128) * byp]; - int maxindex = w * byp; - int index = 0; - - for (int j = 0; j < texHeight; ++j) - { - while (index < maxindex) - { - byte blocktype = br.ReadByte(); //MSB of blocktype: 1 means Raw packet(non-RLE), 0 means RLE packet - - int bytestoread; - int bytestocopy; - - if (blocktype >= 0x80) // run-length packet. - read 1 color and replicate them by bytestocopy - { - int pixel_count_minus_1 = (blocktype - 0x80); - bytestoread = byp; // pixel data - bytestocopy = byp * pixel_count_minus_1; - } - else //raw packet (non run-lenght encoding.) - { - bytestoread = byp * (blocktype + 1); - bytestocopy = 0; - } - - //if (index + bytestoread > maxindex) - // throw new System.ArgumentException ("Corrupt TGA"); - - br.Read(linebuffer, index, bytestoread); - index += bytestoread; - - for (int i = 0; i != bytestocopy; ++i) - { - linebuffer[index + i] = linebuffer[index + i - bytestoread]; - } - index += bytestocopy; - } - if (!bottomUp) - decodeLine(b, texWidth, texHeight, j, byp, linebuffer, ref cd); - else - decodeLine(b, texWidth, texHeight, texHeight - j - 1, byp, linebuffer, ref cd); - - if (index > maxindex) - { - Array.Copy(linebuffer, maxindex, linebuffer, 0, index - maxindex); - index -= maxindex; - } - else - index = 0; - - } - } - catch (System.IO.EndOfStreamException) - { - } - } - static public Color32 makeColor32fromUInt(uint x) { return new Color32( (byte)( (x & 0x00FF0000) >> 16), @@ -730,75 +619,6 @@ namespace OpenMetaverse.Imaging (byte)((x & 0xFF000000) >> 24)); //alpha } - static uint UnpackColor( - uint sourceColor, ref tgaCD cd) - { - if (cd.RMask == 0xFF && cd.GMask == 0xFF && cd.BMask == 0xFF) - { - // Special case to deal with 8-bit TGA files that we treat as alpha masks - return sourceColor << 24; - } - else - { - uint rpermute = (sourceColor << cd.RShift) | (sourceColor >> (32 - cd.RShift)); - uint gpermute = (sourceColor << cd.GShift) | (sourceColor >> (32 - cd.GShift)); - uint bpermute = (sourceColor << cd.BShift) | (sourceColor >> (32 - cd.BShift)); - uint apermute = (sourceColor << cd.AShift) | (sourceColor >> (32 - cd.AShift)); - uint result = - (rpermute & cd.RMask) | (gpermute & cd.GMask) - | (bpermute & cd.BMask) | (apermute & cd.AMask) | cd.FinalOr; - - return result; - } - } - - static unsafe void decodeLine( - Color32[] b, int texWidth, int texHeight, - int line, - int byp, - byte[] data, - ref tgaCD cd) - { - if (cd.NeedNoConvert) - { - // fast copy - uint offset_colorArray_scanline = (uint)(line * texWidth + 0); //should be large enough? - //uint* linep = (uint*)((byte*)b.Scan0.ToPointer() + line * b.Stride); - fixed (byte* ptr = data) - { - uint* sptr = (uint*)ptr; - for (int i = 0; i < texWidth; ++i) - { - b[i + offset_colorArray_scanline] = makeColor32fromUInt(sptr[i]); - } - } - } - else - { - //byte* linep = (byte*)b.Scan0.ToPointer() + line * b.Stride; - uint offset_colorArray_scanline = (uint)(line * texWidth + 0); - - //uint* up = (uint*)linep; - - int rdi = 0; - - fixed (byte* ptr = data) - { - for (int i = 0; i < texWidth; ++i) - { - uint x = 0; - for (int j = 0; j < byp; ++j) - { - x |= ((uint)ptr[rdi]) << (j << 3); //load all bytes that represent the pixel's color into register x. - ++rdi; - } - uint unpackedColoByte = UnpackColor(x, ref cd); - b[i+line*texWidth] = makeColor32fromUInt(unpackedColoByte); - } - } - } - } - } #endif diff --git a/Assets/Plugins/LibreMetaverse/ImportExport/ModelUploader.cs b/Assets/Plugins/LibreMetaverse/ImportExport/ModelUploader.cs index 3f357cc..34fbcce 100644 --- a/Assets/Plugins/LibreMetaverse/ImportExport/ModelUploader.cs +++ b/Assets/Plugins/LibreMetaverse/ImportExport/ModelUploader.cs @@ -26,10 +26,6 @@ using System; using System.Collections.Generic; -using System.Text.RegularExpressions; -using System.IO; -using System.Xml; -using System.Xml.Serialization; using OpenMetaverse.StructuredData; using OpenMetaverse.Http; @@ -149,16 +145,9 @@ namespace OpenMetaverse.ImportExport resources["mesh_list"] = meshList; OSDArray textureList = new OSDArray(); - for (int i = 0; i < Images.Count; i++) + foreach (var img in Images) { - if (upload) - { - textureList.Add(new OSDBinary(Images[i])); - } - else - { - textureList.Add(new OSDBinary(Utils.EmptyBytes)); - } + textureList.Add(upload ? new OSDBinary(img) : new OSDBinary(Utils.EmptyBytes)); } resources["texture_list"] = textureList; @@ -190,9 +179,8 @@ namespace OpenMetaverse.ImportExport return; } - if (result is OSDMap) + if (result is OSDMap res) { - var res = (OSDMap)result; Uri uploader = new Uri(res["uploader"]); PerformUpload(uploader, (contents => { @@ -262,11 +250,11 @@ namespace OpenMetaverse.ImportExport return; } - Logger.Log("Response from mesh upload prepare:\n" + OSDParser.SerializeLLSDNotationFormatted(result), Helpers.LogLevel.Debug); + Logger.Log("Response from mesh upload prepare:" + Environment.NewLine + OSDParser.SerializeLLSDNotationFormatted(result), Helpers.LogLevel.Debug); callback?.Invoke(result); }; - request.BeginGetResponse(req, OSDFormat.Xml, 3 * 60 * 1000); + request.PostRequestAsync(req, OSDFormat.Xml, 3 * 60 * 1000); } @@ -287,11 +275,12 @@ namespace OpenMetaverse.ImportExport return; } OSDMap res = (OSDMap)result; - Logger.Log("Response from mesh upload perform:\n" + OSDParser.SerializeLLSDNotationFormatted(result), Helpers.LogLevel.Debug); + Logger.Log("Response from mesh upload perform:" + Environment.NewLine + + OSDParser.SerializeLLSDNotationFormatted(result), Helpers.LogLevel.Debug); callback?.Invoke(res); }; - request.BeginGetResponse(AssetResources(true), OSDFormat.Xml, 60 * 1000); + request.PostRequestAsync(AssetResources(true), OSDFormat.Xml, 60 * 1000); } diff --git a/Assets/Plugins/LibreMetaverse/Interfaces/IByteBufferPool.cs b/Assets/Plugins/LibreMetaverse/Interfaces/IByteBufferPool.cs index fbfb6fa..63a7747 100644 --- a/Assets/Plugins/LibreMetaverse/Interfaces/IByteBufferPool.cs +++ b/Assets/Plugins/LibreMetaverse/Interfaces/IByteBufferPool.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace OpenMetaverse.Interfaces +namespace OpenMetaverse.Interfaces { /// /// Interface to a class that can supply cached byte arrays diff --git a/Assets/Plugins/LibreMetaverse/Interfaces/IMessage.cs b/Assets/Plugins/LibreMetaverse/Interfaces/IMessage.cs index 56ffbb3..3a61e15 100644 --- a/Assets/Plugins/LibreMetaverse/Interfaces/IMessage.cs +++ b/Assets/Plugins/LibreMetaverse/Interfaces/IMessage.cs @@ -24,9 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using System.Collections.Generic; -using System.Text; using OpenMetaverse.StructuredData; diff --git a/Assets/Plugins/LibreMetaverse/Inventory.cs b/Assets/Plugins/LibreMetaverse/Inventory.cs index cafc48c..3a0a4a6 100644 --- a/Assets/Plugins/LibreMetaverse/Inventory.cs +++ b/Assets/Plugins/LibreMetaverse/Inventory.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -132,7 +133,7 @@ namespace OpenMetaverse set { UpdateNodeFor(value); - _RootNode = Items[value.UUID]; + RootNode = Items[value.UUID]; } } @@ -145,28 +146,25 @@ namespace OpenMetaverse set { UpdateNodeFor(value); - _LibraryRootNode = Items[value.UUID]; + LibraryRootNode = Items[value.UUID]; } } - private InventoryNode _LibraryRootNode; - private InventoryNode _RootNode; - /// /// The root node of the avatars inventory /// - public InventoryNode RootNode => _RootNode; + public InventoryNode RootNode { get; private set; } /// /// The root node of the default shared library /// - public InventoryNode LibraryRootNode => _LibraryRootNode; + public InventoryNode LibraryRootNode { get; private set; } - public UUID Owner { get; } + public UUID Owner { get; private set; } private GridClient Client; //private InventoryManager Manager; - public Dictionary Items = new Dictionary(); + public Dictionary Items; public Inventory(GridClient client, InventoryManager manager) : this(client, manager, client.Self.AgentID) { } @@ -226,8 +224,10 @@ namespace OpenMetaverse if (item.ParentUUID != UUID.Zero && !Items.TryGetValue(item.ParentUUID, out itemParent)) { // OK, we have no data on the parent, let's create a fake one. - InventoryFolder fakeParent = new InventoryFolder(item.ParentUUID); - fakeParent.DescendentCount = 1; // Dear god, please forgive me. + InventoryFolder fakeParent = new InventoryFolder(item.ParentUUID) + { + DescendentCount = 1 // Dear god, please forgive me. + }; itemParent = new InventoryNode(fakeParent); Items[item.ParentUUID] = itemParent; // Unfortunately, this breaks the nice unified tree @@ -235,13 +235,6 @@ namespace OpenMetaverse // As soon as we get the parent, the tree repairs itself. //Logger.DebugLog("Attempting to update inventory child of " + // item.ParentUUID.ToString() + " when we have no local reference to that folder", Client); - - if (Client.Settings.FETCH_MISSING_INVENTORY) - { - // Fetch the parent - List fetchreq = new List(1) {item.ParentUUID}; - - } } InventoryNode itemNode; @@ -349,7 +342,7 @@ namespace OpenMetaverse BinaryFormatter bformatter = new BinaryFormatter(); lock (Items) { - Logger.Log("Caching " + Items.Count.ToString() + " inventory items to " + filename, Helpers.LogLevel.Info); + Logger.Log($"Caching {Items.Count} inventory items to {filename}", Helpers.LogLevel.Info); foreach (KeyValuePair kvp in Items) { bformatter.Serialize(stream, kvp.Value); @@ -359,7 +352,7 @@ namespace OpenMetaverse } catch (Exception e) { - Logger.Log("Error saving inventory cache to disk :"+e.Message,Helpers.LogLevel.Error); + Logger.Log("Error saving inventory cache to disk", Helpers.LogLevel.Error, e); } } @@ -367,7 +360,7 @@ namespace OpenMetaverse /// Loads in inventory cache file into the inventory structure. Note only valid to call after login has been successful. /// /// Name of the cache file to load - /// The number of inventory items sucessfully reconstructed into the inventory node tree + /// The number of inventory items successfully reconstructed into the inventory node tree public int RestoreFromDisk(string filename) { List nodes = new List(); @@ -381,10 +374,9 @@ namespace OpenMetaverse using (Stream stream = File.Open(filename, FileMode.Open)) { BinaryFormatter bformatter = new BinaryFormatter(); - while (stream.Position < stream.Length) { - OpenMetaverse.InventoryNode node = (InventoryNode)bformatter.Deserialize(stream); + var node = (InventoryNode)bformatter.Deserialize(stream); nodes.Add(node); item_count++; } @@ -392,11 +384,11 @@ namespace OpenMetaverse } catch (Exception e) { - Logger.Log("Error accessing inventory cache file :" + e.Message, Helpers.LogLevel.Error); + Logger.Log("Error accessing inventory cache file", Helpers.LogLevel.Error, e); return -1; } - Logger.Log("Read " + item_count.ToString() + " items from inventory cache file", Helpers.LogLevel.Info); + Logger.Log($"Read {item_count} items from inventory cache file", Helpers.LogLevel.Info); item_count = 0; List del_nodes = new List(); //nodes that we have processed and will delete @@ -478,7 +470,7 @@ namespace OpenMetaverse del_nodes.Clear(); } - Logger.Log("Reassembled " + item_count.ToString() + " items from inventory cache file", Helpers.LogLevel.Info); + Logger.Log($"Reassembled {item_count} items from inventory cache file", Helpers.LogLevel.Info); return item_count; } @@ -506,9 +498,10 @@ namespace OpenMetaverse { // Log a warning if there is a UUID mismatch, this will cause problems if (value.UUID != uuid) - Logger.Log("Inventory[uuid]: uuid " + uuid.ToString() + " is not equal to value.UUID " + - value.UUID.ToString(), Helpers.LogLevel.Warning, Client); - + { + Logger.Log($"Inventory[uuid]: uuid {uuid} is not equal to value.UUID {value.UUID}", + Helpers.LogLevel.Warning, Client); + } UpdateNodeFor(value); } else diff --git a/Assets/Plugins/LibreMetaverse/InventoryAISClient.cs b/Assets/Plugins/LibreMetaverse/InventoryAISClient.cs index 96dedc2..4e0e832 100644 --- a/Assets/Plugins/LibreMetaverse/InventoryAISClient.cs +++ b/Assets/Plugins/LibreMetaverse/InventoryAISClient.cs @@ -1,10 +1,33 @@ +/* + * Copyright (c) 2019-2022, Sjofn LLC + * 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.Collections.Generic; -using System.ComponentModel; using System.Linq; -using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using OpenMetaverse; @@ -26,7 +49,7 @@ namespace LibreMetaverse { Client = client; httpClient.DefaultRequestHeaders.Accept.Clear(); - httpClient.DefaultRequestHeaders.Add("User-Agent", "LibreMetaverse AIS Client"); + httpClient.DefaultRequestHeaders.Add("User-Agent", $"{Settings.USER_AGENT} AIS Client"); } public bool IsAvailable => (Client.Network.CurrentSim.Caps != null && diff --git a/Assets/Plugins/LibreMetaverse/InventoryBase.cs b/Assets/Plugins/LibreMetaverse/InventoryBase.cs index a7c8c52..5fd4d94 100644 --- a/Assets/Plugins/LibreMetaverse/InventoryBase.cs +++ b/Assets/Plugins/LibreMetaverse/InventoryBase.cs @@ -1,7 +1,32 @@ -using System; -using System.Collections.Generic; +/* + * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. + * 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.Runtime.Serialization; -using System.Text; using OpenMetaverse.StructuredData; namespace OpenMetaverse @@ -9,7 +34,7 @@ namespace OpenMetaverse /// /// Base Class for Inventory Items /// - [Serializable()] + [Serializable] public abstract class InventoryBase : ISerializable { /// of item/folder @@ -69,7 +94,7 @@ namespace OpenMetaverse } /// - /// Determine whether the specified object is equal to the current object + /// Determine whether the specified object is equal to the current object /// /// InventoryBase object to compare against /// true if objects are the same @@ -79,7 +104,7 @@ namespace OpenMetaverse } /// - /// Determine whether the specified object is equal to the current object + /// Determine whether the specified object is equal to the current object /// /// InventoryBase object to compare against /// true if objects are the same @@ -106,10 +131,9 @@ namespace OpenMetaverse { public override string ToString() { - return AssetType + " " + AssetUUID + " (" + InventoryType + " " + UUID + ") '" + Name + "'/'" + - Description + "' " + Permissions; + return $"{AssetType} {AssetUUID} ({InventoryType} {UUID}) '{Name}'/'{Description}' {Permissions}"; } - /// The of this item + /// The of this item public UUID AssetUUID; /// The combined of this item public Permissions Permissions; @@ -117,11 +141,11 @@ namespace OpenMetaverse public AssetType AssetType; /// The type of item from the enum public InventoryType InventoryType; - /// The of the creator of this item + /// The of the creator of this item public UUID CreatorID; /// A Description of this item public string Description; - /// The s this item is set to or owned by + /// The s this item is set to or owned by public UUID GroupID; /// If true, item is owned by a group public bool GroupOwned; @@ -129,20 +153,20 @@ namespace OpenMetaverse public int SalePrice; /// The type of sale from the enum public SaleType SaleType; - /// Combined flags from + /// Combined flags from public uint Flags; /// Time and date this inventory item was created, stored as /// UTC (Coordinated Universal Time) public DateTime CreationDate; /// Used to update the AssetID in requests sent to the server public UUID TransactionID; - /// The of the previous owner of the item + /// The of the previous owner of the item public UUID LastOwnerID; /// /// Construct a new InventoryItem object /// - /// The of the item + /// The of the item public InventoryItem(UUID itemID) : base(itemID) { } @@ -245,9 +269,9 @@ namespace OpenMetaverse } /// - /// Determine whether the specified object is equal to the current object + /// Determine whether the specified object is equal to the current object /// - /// The object to compare against + /// The object to compare against /// true if objects are the same public bool Equals(InventoryItem o) { @@ -348,14 +372,14 @@ namespace OpenMetaverse /// InventoryTexture Class representing a graphical image /// /// - [Serializable()] + [Serializable] public class InventoryTexture : InventoryItem { /// /// Construct an InventoryTexture object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryTexture(UUID itemID) : base(itemID) { @@ -376,14 +400,14 @@ namespace OpenMetaverse /// /// InventorySound Class representing a playable sound /// - [Serializable()] + [Serializable] public class InventorySound : InventoryItem { /// /// Construct an InventorySound object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventorySound(UUID itemID) : base(itemID) { @@ -404,14 +428,14 @@ namespace OpenMetaverse /// /// InventoryCallingCard Class, contains information on another avatar /// - [Serializable()] + [Serializable] public class InventoryCallingCard : InventoryItem { /// /// Construct an InventoryCallingCard object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryCallingCard(UUID itemID) : base(itemID) { @@ -432,14 +456,14 @@ namespace OpenMetaverse /// /// InventoryLandmark Class, contains details on a specific location /// - [Serializable()] + [Serializable] public class InventoryLandmark : InventoryItem { /// /// Construct an InventoryLandmark object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryLandmark(UUID itemID) : base(itemID) { @@ -474,14 +498,14 @@ namespace OpenMetaverse /// /// InventoryObject Class contains details on a primitive or coalesced set of primitives /// - [Serializable()] + [Serializable] public class InventoryObject : InventoryItem { /// /// Construct an InventoryObject object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryObject(UUID itemID) : base(itemID) { @@ -520,14 +544,14 @@ namespace OpenMetaverse /// /// InventoryNotecard Class, contains details on an encoded text document /// - [Serializable()] + [Serializable] public class InventoryNotecard : InventoryItem { /// /// Construct an InventoryNotecard object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryNotecard(UUID itemID) : base(itemID) { @@ -549,14 +573,14 @@ namespace OpenMetaverse /// InventoryCategory Class /// /// TODO: Is this even used for anything? - [Serializable()] + [Serializable] public class InventoryCategory : InventoryItem { /// /// Construct an InventoryCategory object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryCategory(UUID itemID) : base(itemID) { @@ -577,14 +601,14 @@ namespace OpenMetaverse /// /// InventoryLSL Class, represents a Linden Scripting Language object /// - [Serializable()] + [Serializable] public class InventoryLSL : InventoryItem { /// /// Construct an InventoryLSL object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryLSL(UUID itemID) : base(itemID) { @@ -605,7 +629,7 @@ namespace OpenMetaverse /// /// InventorySnapshot Class, an image taken with the viewer /// - [Serializable()] + [Serializable] public class InventorySnapshot : InventoryItem { /// @@ -633,14 +657,14 @@ namespace OpenMetaverse /// /// InventoryAttachment Class, contains details on an attachable object /// - [Serializable()] + [Serializable] public class InventoryAttachment : InventoryItem { /// /// Construct an InventoryAttachment object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryAttachment(UUID itemID) : base(itemID) { @@ -670,14 +694,14 @@ namespace OpenMetaverse /// /// InventoryWearable Class, details on a clothing item or body part /// - [Serializable()] + [Serializable] public class InventoryWearable : InventoryItem { /// /// Construct an InventoryWearable object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryWearable(UUID itemID) : base(itemID) { InventoryType = InventoryType.Wearable; } /// @@ -703,14 +727,14 @@ namespace OpenMetaverse /// /// InventoryAnimation Class, A bvh encoded object which animates an avatar /// - [Serializable()] + [Serializable] public class InventoryAnimation : InventoryItem { /// /// Construct an InventoryAnimation object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryAnimation(UUID itemID) : base(itemID) { @@ -731,14 +755,14 @@ namespace OpenMetaverse /// /// InventoryGesture Class, details on a series of animations, sounds, and actions /// - [Serializable()] + [Serializable] public class InventoryGesture : InventoryItem { /// /// Construct an InventoryGesture object /// - /// A which becomes the - /// objects AssetUUID + /// A which becomes the + /// objects AssetUUID public InventoryGesture(UUID itemID) : base(itemID) { @@ -760,7 +784,7 @@ namespace OpenMetaverse /// A folder contains s and has certain attributes specific /// to itself /// - [Serializable()] + [Serializable] public class InventoryFolder : InventoryBase { /// The Preferred for a folder. diff --git a/Assets/Plugins/LibreMetaverse/InventoryManager.cs b/Assets/Plugins/LibreMetaverse/InventoryManager.cs index 416b873..9eeb3bf 100644 --- a/Assets/Plugins/LibreMetaverse/InventoryManager.cs +++ b/Assets/Plugins/LibreMetaverse/InventoryManager.cs @@ -29,8 +29,6 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading; -using System.Runtime.Serialization; -using System.Threading.Tasks; using OpenMetaverse.Http; using OpenMetaverse.Messages.Linden; using OpenMetaverse.StructuredData; @@ -548,8 +546,7 @@ namespace OpenMetaverse if (itemIDs.Count != ownerIDs.Count) throw new ArgumentException("itemIDs and ownerIDs must contain the same number of entries"); - if (Client.Settings.HTTP_INVENTORY && - Client.Network.CurrentSim.Caps != null && + if (Client.Network.CurrentSim.Caps != null && Client.Network.CurrentSim.Caps.CapabilityURI("FetchInventory2") != null) { RequestFetchInventoryCap(itemIDs, ownerIDs); @@ -590,8 +587,7 @@ namespace OpenMetaverse if (itemIDs.Count != ownerIDs.Count) throw new ArgumentException("itemIDs and ownerIDs must contain the same number of entries"); - if (Client.Settings.HTTP_INVENTORY && - Client.Network.CurrentSim.Caps != null && + if (Client.Network.CurrentSim.Caps != null && Client.Network.CurrentSim.Caps.CapabilityURI("FetchInventory2") != null) { CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("FetchInventory2"); @@ -633,7 +629,7 @@ namespace OpenMetaverse OSDRequest["items"] = items; - request.BeginGetResponse(OSDRequest, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(OSDRequest, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } } /// @@ -647,7 +643,7 @@ namespace OpenMetaverse /// sort order to return results in /// a integer representing the number of milliseconds to wait for results /// when false uses links and does not attempt to get the real object - /// keypair with a status message, and List + /// keypair with a status message, and List /// if the status message is retry you should check again in a bit is is currently loading /// links using RequestFetchInventoryCap /// @@ -764,8 +760,7 @@ namespace OpenMetaverse { string cap = owner == Client.Self.AgentID ? "FetchInventoryDescendents2" : "FetchLibDescendents2"; - if (Client.Settings.HTTP_INVENTORY && - Client.Network.CurrentSim.Caps != null && + if (Client.Network.CurrentSim.Caps != null && Client.Network.CurrentSim.Caps.CapabilityURI(cap) != null) { RequestFolderContentsCap(folder, owner, folders, items, order); @@ -909,8 +904,8 @@ namespace OpenMetaverse } catch (Exception exc) { - Logger.Log($"Failed to fetch inventory descendants: {exc.Message}\n" + - $"{exc.StackTrace.ToString()}", + Logger.Log($"Failed to fetch inventory descendants: {exc.Message}" + Environment.NewLine + + $"{exc.StackTrace}", Helpers.LogLevel.Warning, Client); foreach (var f in batch) { @@ -937,11 +932,11 @@ namespace OpenMetaverse } OSDMap req = new OSDMap(1) { ["folders"] = requestedFolders }; - request.BeginGetResponse(req, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(req, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } catch (Exception ex) { - Logger.Log($"Failed to fetch inventory descendants: {ex.Message}\n" + + Logger.Log($"Failed to fetch inventory descendants: {ex.Message}" + Environment.NewLine + $"{ex.StackTrace}", Helpers.LogLevel.Warning, Client); foreach (var f in batch) @@ -1778,31 +1773,29 @@ namespace OpenMetaverse CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("NewFileAgentInventory"); - if (request != null) - { - OSDMap query = new OSDMap - { - {"folder_id", OSD.FromUUID(folderID)}, - {"asset_type", OSD.FromString(Utils.AssetTypeToString(assetType))}, - {"inventory_type", OSD.FromString(Utils.InventoryTypeToString(invType))}, - {"name", OSD.FromString(name)}, - {"description", OSD.FromString(description)}, - {"everyone_mask", OSD.FromInteger((int) permissions.EveryoneMask)}, - {"group_mask", OSD.FromInteger((int) permissions.GroupMask)}, - {"next_owner_mask", OSD.FromInteger((int) permissions.NextOwnerMask)}, - {"expected_upload_cost", OSD.FromInteger(Client.Settings.UPLOAD_COST)} - }; - - // Make the request - request.OnComplete += CreateItemFromAssetResponse; - request.UserData = new object[] { callback, data, Client.Settings.CAPS_TIMEOUT, query }; - - request.BeginGetResponse(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); - } - else - { + if (request == null) { throw new Exception("NewFileAgentInventory capability is not currently available"); } + + OSDMap query = new OSDMap + { + {"folder_id", OSD.FromUUID(folderID)}, + {"asset_type", OSD.FromString(Utils.AssetTypeToString(assetType))}, + {"inventory_type", OSD.FromString(Utils.InventoryTypeToString(invType))}, + {"name", OSD.FromString(name)}, + {"description", OSD.FromString(description)}, + {"everyone_mask", OSD.FromInteger((int) permissions.EveryoneMask)}, + {"group_mask", OSD.FromInteger((int) permissions.GroupMask)}, + {"next_owner_mask", OSD.FromInteger((int) permissions.NextOwnerMask)}, + {"expected_upload_cost", OSD.FromInteger(Client.Settings.UPLOAD_COST)} + }; + + // Make the request + request.OnComplete += CreateItemFromAssetResponse; + request.UserData = new object[] { callback, data, Client.Settings.CAPS_TIMEOUT, query }; + + request.PostRequestAsync(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + } /// @@ -2009,7 +2002,7 @@ namespace OpenMetaverse ObjectID = objectID }; - request.BeginGetResponse(message.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(message.Serialize(),OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -2163,7 +2156,7 @@ namespace OpenMetaverse // Make the request request.OnComplete += UploadInventoryAssetResponse; request.UserData = new object[] { new KeyValuePair(callback, data), notecardID }; - request.BeginGetResponse(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -2196,7 +2189,7 @@ namespace OpenMetaverse // Make the request request.OnComplete += UploadInventoryAssetResponse; request.UserData = new object[] { new KeyValuePair(callback, data), notecardID }; - request.BeginGetResponse(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -2224,7 +2217,7 @@ namespace OpenMetaverse // Make the request request.OnComplete += UploadInventoryAssetResponse; request.UserData = new object[] { new KeyValuePair(callback, data), gestureID }; - request.BeginGetResponse(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(query, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -2253,7 +2246,7 @@ namespace OpenMetaverse request.OnComplete += new CapsClient.CompleteCallback(UpdateScriptAgentInventoryResponse); request.UserData = new object[2] { new KeyValuePair(callback, data), itemID }; - request.BeginGetResponse(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -2286,7 +2279,7 @@ namespace OpenMetaverse request.OnComplete += new CapsClient.CompleteCallback(UpdateScriptAgentInventoryResponse); request.UserData = new object[2] { new KeyValuePair(callback, data), itemID }; - request.BeginGetResponse(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -2563,7 +2556,7 @@ namespace OpenMetaverse /// /// Recurse inventory category and return folders and items. Does NOT contain parent folder being searched /// - /// Inventory category to recursively search + /// Inventory category to recursively search /// Owner of folder /// reference to list of categories /// reference to list of items @@ -3589,6 +3582,7 @@ namespace OpenMetaverse break; } imp.MessageBlock.BinaryBucket = args.FolderID.GetBytes(); + RequestFetchInventory(objectID, e.IM.ToAgentID); } else { @@ -3609,7 +3603,7 @@ namespace OpenMetaverse imp.MessageBlock.BinaryBucket = Utils.EmptyBytes; } - Client.Network.SendPacket(imp, e.Simulator); + Client.Network.SendPacket(imp, e.Simulator ?? Client.Network.CurrentSim); } catch (Exception ex) { @@ -3660,7 +3654,7 @@ namespace OpenMetaverse CapsClient upload = new CapsClient(new Uri(uploadURL), "CreateItemFromAsset"); upload.OnComplete += CreateItemFromAssetResponse; upload.UserData = new object[] { callback, itemData, millisecondsTimeout, request }; - upload.BeginGetResponse(itemData, "application/octet-stream", millisecondsTimeout); + upload.PostRequestAsync(itemData, "application/octet-stream", millisecondsTimeout); } else if (status == "complete") { @@ -3739,7 +3733,7 @@ namespace OpenMetaverse CapsClient upload = new CapsClient(uploadURL, "UploadItemResponse"); upload.OnComplete += UploadInventoryAssetResponse; upload.UserData = new object[2] { kvp, (UUID)(((object[])client.UserData)[1]) }; - upload.BeginGetResponse(itemData, "application/octet-stream", Client.Settings.CAPS_TIMEOUT); + upload.PostRequestAsync(itemData, "application/octet-stream", Client.Settings.CAPS_TIMEOUT); } else { @@ -3810,7 +3804,7 @@ namespace OpenMetaverse CapsClient upload = new CapsClient(new Uri(uploadURL), "ScriptAgentInventoryResponse"); upload.OnComplete += UpdateScriptAgentInventoryResponse; upload.UserData = new object[2] { kvp, (UUID)(((object[])client.UserData)[1]) }; - upload.BeginGetResponse(itemData, "application/octet-stream", Client.Settings.CAPS_TIMEOUT); + upload.PostRequestAsync(itemData, "application/octet-stream", Client.Settings.CAPS_TIMEOUT); } else if (status == "complete" && callback != null) { diff --git a/Assets/Plugins/LibreMetaverse/InventoryNode.cs b/Assets/Plugins/LibreMetaverse/InventoryNode.cs index d4dfb5d..7eb27f0 100644 --- a/Assets/Plugins/LibreMetaverse/InventoryNode.cs +++ b/Assets/Plugins/LibreMetaverse/InventoryNode.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -34,13 +35,12 @@ namespace OpenMetaverse { private InventoryBase data; private InventoryNode parent; - private UUID parentID; //used for de-seralization + private UUID parentID; //used for deseralization private InventoryNodeDictionary nodes; private bool needsUpdate = true; [NonSerialized] private object tag; - /// public InventoryBase Data { get => data; @@ -54,24 +54,35 @@ namespace OpenMetaverse set => tag = value; } - /// public InventoryNode Parent { get => parent; set => parent = value; } - /// - public UUID ParentID => parentID; + public UUID ParentID + { + get => parentID; + private set => parentID = value; + } - /// public InventoryNodeDictionary Nodes { - get { return nodes ?? (nodes = new InventoryNodeDictionary(this)); } + get => nodes ?? (nodes = new InventoryNodeDictionary(this)); set => nodes = value; } - public System.DateTime ModifyTime + /// + /// For inventory folder nodes specifies weather the folder needs to be + /// refreshed from the server + /// + public bool NeedsUpdate + { + get => needsUpdate; + set => needsUpdate = value; + } + + public DateTime ModifyTime { get { @@ -97,16 +108,6 @@ namespace OpenMetaverse Nodes.Sort(); } - /// - /// For inventory folder nodes specifies weather the folder needs to be - /// refreshed from the server - /// - public bool NeedsUpdate - { - get { return needsUpdate; } - set { needsUpdate = value; } - } - public InventoryNode() { } @@ -151,8 +152,8 @@ namespace OpenMetaverse Type type = (Type)info.GetValue("Type", typeof(Type)); // Construct a new inventory object based on the Type stored in Type - System.Reflection.ConstructorInfo ctr = type.GetConstructor(new Type[] {typeof(SerializationInfo),typeof(StreamingContext)}); - data = (InventoryBase) ctr.Invoke(new Object[] { info, ctxt }); + System.Reflection.ConstructorInfo ctr = type.GetConstructor(new[] {typeof(SerializationInfo),typeof(StreamingContext)}); + if (ctr != null) data = (InventoryBase)ctr.Invoke(new object[] { info, ctxt }); } public override string ToString() diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Rendering.Meshmerizer/MeshmerizerR.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Rendering.Meshmerizer/MeshmerizerR.cs index 81974a4..43150b8 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Rendering.Meshmerizer/MeshmerizerR.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Rendering.Meshmerizer/MeshmerizerR.cs @@ -1,4 +1,6 @@ /* Copyright (c) 2008 Robert Adams + * Copyright (c) 2021-2022, Sjofn LLC. 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 @@ -37,8 +39,6 @@ using System.Collections.Generic; using System.IO; using OpenMetaverse.StructuredData; using LibreMetaverse.PrimMesher; -using OMV = OpenMetaverse; -using OMVR = OpenMetaverse.Rendering; //using Catnip.Drawing; using UnityEngine; @@ -48,7 +48,7 @@ namespace OpenMetaverse.Rendering /// Meshing code based on the Idealist Viewer (20081213). /// [RendererName("MeshmerizerR")] - public class MeshmerizerR : OMVR.IRendering + public class MeshmerizerR : IRendering { /// /// Generates a basic mesh structure from a primitive @@ -57,9 +57,9 @@ namespace OpenMetaverse.Rendering /// Primitive to generate the mesh from /// Level of detail to generate the mesh at /// The generated mesh or null on failure - public OMVR.SimpleMesh GenerateSimpleMesh(OMV.Primitive prim, OMVR.DetailLevel lod) + public SimpleMesh GenerateSimpleMesh(Primitive prim, DetailLevel lod) { - LibreMetaverse.PrimMesher.PrimMesh newPrim = GeneratePrimMesh(prim, lod, false); + PrimMesh newPrim = GeneratePrimMesh(prim, lod, false); if (newPrim == null) return null; @@ -86,6 +86,44 @@ namespace OpenMetaverse.Rendering return mesh; } + + /// + /// Generates a basic mesh structure from a primitive, adding normals data. + /// A 'SimpleMesh' is just the prim's overall shape with no material information. + /// + /// Primitive to generate the mesh from + /// Level of detail to generate the mesh at + /// The generated mesh or null on failure + public SimpleMesh GenerateSimpleMeshWithNormals(Primitive prim, DetailLevel lod) { + PrimMesh newPrim = GeneratePrimMesh(prim, lod, true); + if(newPrim == null) + return null; + + SimpleMesh mesh = new SimpleMesh { + Path = new Path(), + Prim = prim, + Profile = new Profile(), + Vertices = new List(newPrim.coords.Count) + }; + + for(int i = 0; i < newPrim.coords.Count; i++) { + Coord c = newPrim.coords[i]; + // Also saving the normal within the vertice + Coord n = newPrim.normals[i]; + mesh.Vertices.Add(new Vertex {Position = new Vector3(c.X, c.Y, c.Z), Normal = new Vector3(n.X, n.Y, n.Z)}); + } + + mesh.Indices = new List(newPrim.faces.Count * 3); + foreach(var face in newPrim.faces) { + mesh.Indices.Add((ushort) face.v1); + mesh.Indices.Add((ushort) face.v2); + mesh.Indices.Add((ushort) face.v3); + } + + return mesh; + } + + /// /// Generates a basic mesh structure from a sculpted primitive. /// 'SimpleMesh's have a single mesh and no faces or material information. @@ -94,7 +132,9 @@ namespace OpenMetaverse.Rendering /// Sculpt texture /// Level of detail to generate the mesh at /// The generated mesh or null on failure - public SimpleMesh GenerateSimpleSculptMesh(Primitive prim, Texture2D sculptTexture, DetailLevel lod) + public SimpleMesh GenerateSimpleSculptMesh( Primitive prim, + Texture2D sculptTexture, + DetailLevel lod) { var faceted = GenerateFacetedSculptMesh(prim, sculptTexture, lod); @@ -130,41 +170,41 @@ namespace OpenMetaverse.Rendering /// The generated mesh public FacetedMesh GenerateFacetedMesh(Primitive prim, DetailLevel lod) { - bool isSphere = ((OMV.ProfileCurve)(prim.PrimData.profileCurve & 0x07) == OMV.ProfileCurve.HalfCircle); - LibreMetaverse.PrimMesher.PrimMesh newPrim = GeneratePrimMesh(prim, lod, true); + bool isSphere = ((ProfileCurve)(prim.PrimData.profileCurve & 0x07) == ProfileCurve.HalfCircle); + PrimMesh newPrim = GeneratePrimMesh(prim, lod, true); if (newPrim == null) return null; - // copy the vertex information into OMVR.IRendering structures - var omvrmesh = new OMVR.FacetedMesh + // copy the vertex information into IRendering structures + var omvrmesh = new FacetedMesh { - Faces = new List(), + Faces = new List(), Prim = prim, - Profile = new OMVR.Profile + Profile = new Profile { - Faces = new List(), - Positions = new List() + Faces = new List(), + Positions = new List() }, - Path = new OMVR.Path {Points = new List()} + Path = new Path {Points = new List()} }; var indexer = newPrim.GetVertexIndexer(); for (int i = 0; i < indexer.numPrimFaces; i++) { - OMVR.Face oface = new OMVR.Face + Face oface = new Face { - Vertices = new List(), + Vertices = new List(), Indices = new List(), TextureFace = prim.Textures.GetFace((uint) i) }; for (int j = 0; j < indexer.viewerVertices[i].Count; j++) { - var vert = new OMVR.Vertex(); + var vert = new Vertex(); var m = indexer.viewerVertices[i][j]; vert.Position = new Vector3(m.v.X, m.v.Y, m.v.Z); vert.Normal = new Vector3(m.n.X, m.n.Y, m.n.Z); - vert.TexCoord = new OMV.Vector2(m.uv.U, 1.0f - m.uv.V); + vert.TexCoord = new Vector2(m.uv.U, 1.0f - m.uv.V); oface.Vertices.Add(vert); } @@ -189,25 +229,27 @@ namespace OpenMetaverse.Rendering /// routine since all the context for finding teh texture is elsewhere. /// /// The faceted mesh or null if can't do it - public OMVR.FacetedMesh GenerateFacetedSculptMesh(Primitive prim, Texture2D scupltTexture, DetailLevel lod) + public FacetedMesh GenerateFacetedSculptMesh( Primitive prim, + Texture2D scupltTexture, + DetailLevel lod) { LibreMetaverse.PrimMesher.SculptMesh.SculptType smSculptType; switch (prim.Sculpt.Type) { case SculptType.Cylinder: - smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.cylinder; + smSculptType = SculptMesh.SculptType.cylinder; break; case SculptType.Plane: - smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.plane; + smSculptType = SculptMesh.SculptType.plane; break; case SculptType.Sphere: - smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.sphere; + smSculptType = SculptMesh.SculptType.sphere; break; case SculptType.Torus: - smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.torus; + smSculptType = SculptMesh.SculptType.torus; break; default: - smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.plane; + smSculptType = SculptMesh.SculptType.plane; break; } // The lod for sculpties is the resolution of the texture passed. @@ -216,40 +258,40 @@ namespace OpenMetaverse.Rendering int mesherLod = 32; // number used in Idealist viewer switch (lod) { - case OMVR.DetailLevel.Highest: + case DetailLevel.Highest: break; - case OMVR.DetailLevel.High: + case DetailLevel.High: break; - case OMVR.DetailLevel.Medium: + case DetailLevel.Medium: mesherLod /= 2; break; - case OMVR.DetailLevel.Low: + case DetailLevel.Low: mesherLod /= 4; break; } - LibreMetaverse.PrimMesher.SculptMesh newMesh = - new LibreMetaverse.PrimMesher.SculptMesh(scupltTexture, smSculptType, mesherLod, true, prim.Sculpt.Mirror, prim.Sculpt.Invert); + SculptMesh newMesh = + new SculptMesh(scupltTexture, smSculptType, mesherLod, true, prim.Sculpt.Mirror, prim.Sculpt.Invert); int numPrimFaces = 1; // a scuplty has only one face - // copy the vertex information into OMVR.IRendering structures - FacetedMesh omvrmesh = new OMVR.FacetedMesh + // copy the vertex information into IRendering structures + FacetedMesh omvrmesh = new FacetedMesh { - Faces = new List(), + Faces = new List(), Prim = prim, - Profile = new OMVR.Profile + Profile = new Profile { - Faces = new List(), - Positions = new List() + Faces = new List(), + Positions = new List() }, - Path = new OMVR.Path {Points = new List()} + Path = new Path {Points = new List()} }; for (int ii = 0; ii < numPrimFaces; ii++) { - Face oface = new OMVR.Face + Face oface = new Face { - Vertices = new List(), + Vertices = new List(), Indices = new List(), TextureFace = prim.Textures.GetFace((uint) ii) }; @@ -257,7 +299,7 @@ namespace OpenMetaverse.Rendering for (int j = 0; j < faceVertices; j++) { - var vert = new OMVR.Vertex + var vert = new Vertex { Position = new Vector3(newMesh.coords[j].X, newMesh.coords[j].Y, newMesh.coords[j].Z), Normal = new Vector3(newMesh.normals[j].X, newMesh.normals[j].Y, newMesh.normals[j].Z), @@ -286,12 +328,16 @@ namespace OpenMetaverse.Rendering /// /// Apply texture coordinate modifications from a - /// to a list of vertices + /// to a list of vertices /// /// Vertex list to modify texture coordinates for /// Center-point of the face /// Face texture parameters - public void TransformTexCoords(List vertices, OMV.Vector3 center, OMV.Primitive.TextureEntryFace teFace, Vector3 primScale) + /// Prim scale vector + public void TransformTexCoords( List vertices, + Vector3 center, + Primitive.TextureEntryFace teFace, + Vector3 primScale) { // compute trig stuff up front float cosineAngle = (float)Math.Cos(teFace.Rotation); @@ -346,7 +392,7 @@ namespace OpenMetaverse.Rendering // var facetedMesh = MeshSubMeshAsFacetedMesh(prim, meshParts["medium_lod"]): // 3. Get a simple mesh from one of the submeshes (good if just getting a physics version): // OSDMap meshParts = UnpackMesh(meshData); - // OMV.Mesh flatMesh = MeshSubMeshAsSimpleMesh(prim, meshParts["physics_mesh"]); + // Mesh flatMesh = MeshSubMeshAsSimpleMesh(prim, meshParts["physics_mesh"]); // // "physics_convex" is specially formatted so there is another routine to unpack // that section: @@ -364,9 +410,9 @@ namespace OpenMetaverse.Rendering /// routine since all the context for finding the data is elsewhere. /// /// The faceted mesh or null if can't do it - public OMVR.FacetedMesh GenerateFacetedMeshMesh(OMV.Primitive prim, byte[] meshData) + public FacetedMesh GenerateFacetedMeshMesh(Primitive prim, byte[] meshData) { - OMVR.FacetedMesh ret = null; + FacetedMesh ret = null; OSDMap meshParts = UnpackMesh(meshData); if (meshParts != null) { @@ -391,18 +437,18 @@ namespace OpenMetaverse.Rendering // A version of GenerateFacetedMeshMesh that takes LOD spec so it's similar in calling convention of // the other Generate* methods. - public OMVR.FacetedMesh GenerateFacetedMeshMesh(OMV.Primitive prim, byte[] meshData, OMVR.DetailLevel lod) { - OMVR.FacetedMesh ret = null; + public FacetedMesh GenerateFacetedMeshMesh(Primitive prim, byte[] meshData, DetailLevel lod) { + FacetedMesh ret = null; string partName = null; switch (lod) { - case OMVR.DetailLevel.Highest: + case DetailLevel.Highest: partName = "high_lod"; break; - case OMVR.DetailLevel.High: + case DetailLevel.High: partName = "medium_lod"; break; - case OMVR.DetailLevel.Medium: + case DetailLevel.Medium: partName = "low_lod"; break; - case OMVR.DetailLevel.Low: + case DetailLevel.Low: partName = "lowest_lod"; break; } if (partName != null) @@ -424,7 +470,7 @@ namespace OpenMetaverse.Rendering } // Convert a compressed submesh buffer into a FacetedMesh. - public FacetedMesh MeshSubMeshAsFacetedMesh(OMV.Primitive prim, byte[] compressedMeshData) + public FacetedMesh MeshSubMeshAsFacetedMesh(Primitive prim, byte[] compressedMeshData) { FacetedMesh ret = null; OSD meshOSD = Helpers.DecompressOSD(compressedMeshData); @@ -443,24 +489,27 @@ namespace OpenMetaverse.Rendering // Convert a compressed submesh buffer into a SimpleMesh. - public OMVR.SimpleMesh MeshSubMeshAsSimpleMesh(OMV.Primitive prim, byte[] compressedMeshData) + public SimpleMesh MeshSubMeshAsSimpleMesh(Primitive prim, byte[] compressedMeshData) { - OMVR.SimpleMesh ret = null; + SimpleMesh ret = null; OSD meshOSD = Helpers.DecompressOSD(compressedMeshData); OSDArray meshFaces = meshOSD as OSDArray; if (meshOSD != null) { ret = new SimpleMesh(); - foreach (OSD subMesh in meshFaces) + if (meshFaces != null) { - AddSubMesh(subMesh, ref ret); + foreach (OSD subMesh in meshFaces) + { + AddSubMesh(subMesh, ref ret); + } } } return ret; } - public List> MeshSubMeshAsConvexHulls(OMV.Primitive prim, byte[] compressedMeshData) + public List> MeshSubMeshAsConvexHulls(Primitive prim, byte[] compressedMeshData) { List> hulls = new List>(); try { @@ -529,7 +578,7 @@ namespace OpenMetaverse.Rendering } // Add the submesh to the passed SimpleMesh - private void AddSubMesh(OSD subMeshOsd, ref OMVR.SimpleMesh holdingMesh) { + private void AddSubMesh(OSD subMeshOsd, ref SimpleMesh holdingMesh) { if (subMeshOsd is OSDMap subMeshMap) { // As per http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format, some Mesh Level @@ -544,7 +593,7 @@ namespace OpenMetaverse.Rendering } // Add the submesh to the passed FacetedMesh as a new face. - private void AddSubMesh(OMV.Primitive prim, int faceIndex, OSD subMeshOsd, ref OMVR.FacetedMesh holdingMesh) { + private void AddSubMesh(Primitive prim, int faceIndex, OSD subMeshOsd, ref FacetedMesh holdingMesh) { if (subMeshOsd is OSDMap subMesh) { // As per http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format, some Mesh Level @@ -673,13 +722,11 @@ namespace OpenMetaverse.Rendering return indices; } - /// - /// Decodes mesh asset. + /// Decodes mesh asset. /// OSDMap of all of the submeshes in the mesh. The value of the submesh name /// is the uncompressed data for that mesh. /// The OSDMap is made up of the asset_header section (which includes a lot of stuff) - /// plus each of the submeshes unpacked into compressed byte arrays. - /// + /// plus each of the submeshes unpacked into compressed byte arrays. public OSDMap UnpackMesh(byte[] assetData) { OSDMap meshData = new OSDMap(); @@ -723,7 +770,7 @@ namespace OpenMetaverse.Rendering // Local routine to create a mesh from prim parameters. // Collects parameters and calls PrimMesher to create all the faces of the prim. - private LibreMetaverse.PrimMesher.PrimMesh GeneratePrimMesh(Primitive prim, DetailLevel lod, bool viewerMode) + private PrimMesh GeneratePrimMesh(Primitive prim, DetailLevel lod, bool viewerMode) { Primitive.ConstructionData primData = prim.PrimData; int sides = 4; @@ -734,14 +781,14 @@ namespace OpenMetaverse.Rendering bool isSphere = false; - if ((ProfileCurve)(primData.profileCurve & 0x07) == OMV.ProfileCurve.Circle) + if ((ProfileCurve)(primData.profileCurve & 0x07) == ProfileCurve.Circle) { switch (lod) { - case OMVR.DetailLevel.Low: + case DetailLevel.Low: sides = 6; break; - case OMVR.DetailLevel.Medium: + case DetailLevel.Medium: sides = 12; break; default: @@ -749,18 +796,18 @@ namespace OpenMetaverse.Rendering break; } } - else if ((ProfileCurve)(primData.profileCurve & 0x07) == OMV.ProfileCurve.EqualTriangle) + else if ((ProfileCurve)(primData.profileCurve & 0x07) == ProfileCurve.EqualTriangle) sides = 3; - else if ((ProfileCurve)(primData.profileCurve & 0x07) == OMV.ProfileCurve.HalfCircle) + else if ((ProfileCurve)(primData.profileCurve & 0x07) == ProfileCurve.HalfCircle) { // half circle, prim is a sphere isSphere = true; switch (lod) { - case OMVR.DetailLevel.Low: + case DetailLevel.Low: sides = 6; break; - case OMVR.DetailLevel.Medium: + case DetailLevel.Medium: sides = 12; break; default: @@ -858,7 +905,7 @@ namespace OpenMetaverse.Rendering for (int j = 0; j < faceVertices; j++) { - var vert = new OMVR.Vertex + var vert = new Vertex { Position = new Vector3(newMesh.coords[j].X, newMesh.coords[j].Y, newMesh.coords[j].Z), Normal = new Vector3(newMesh.normals[j].X, newMesh.normals[j].Y, newMesh.normals[j].Z), diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonData.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonData.cs index 64bfe96..51ce37d 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonData.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonData.cs @@ -122,18 +122,16 @@ namespace LitJson #region IDictionary Indexer object IDictionary.this[object key] { - get { - return EnsureDictionary ()[key]; - } + get => EnsureDictionary ()[key]; set { - if (! (key is String)) + if (! (key is string str)) throw new ArgumentException ( "The key has to be a string"); JsonData data = ToJsonData (value); - this[(string) key] = data; + this[str] = data; } } #endregion diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonMapper.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonMapper.cs index 4be58aa..6473767 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonMapper.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonMapper.cs @@ -30,8 +30,6 @@ namespace LitJson internal struct ArrayMetadata { private Type element_type; - private bool is_array; - private bool is_list; public Type ElementType { @@ -45,24 +43,15 @@ namespace LitJson set { element_type = value; } } - public bool IsArray { - get { return is_array; } - set { is_array = value; } - } + public bool IsArray { get; set; } - public bool IsList { - get { return is_list; } - set { is_list = value; } - } + public bool IsList { get; set; } } internal struct ObjectMetadata { private Type element_type; - private bool is_dictionary; - - private IDictionary properties; public Type ElementType { @@ -76,15 +65,9 @@ namespace LitJson set { element_type = value; } } - public bool IsDictionary { - get { return is_dictionary; } - set { is_dictionary = value; } - } + public bool IsDictionary { get; set; } - public IDictionary Properties { - get { return properties; } - set { properties = value; } - } + public IDictionary Properties { get; set; } } diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonReader.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonReader.cs index a327300..75052bb 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonReader.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/JSON/JsonReader.cs @@ -46,16 +46,13 @@ namespace LitJson private Stack automaton_stack; private int current_input; private int current_symbol; - private bool end_of_json; - private bool end_of_input; private Lexer lexer; private bool parser_in_string; private bool parser_return; private bool read_started; private TextReader reader; private bool reader_is_owned; - private object token_value; - private JsonToken token; + #endregion @@ -70,13 +67,13 @@ namespace LitJson set { lexer.AllowSingleQuotedStrings = value; } } - public bool EndOfInput => end_of_input; + public bool EndOfInput { get; private set; } - public bool EndOfJson => end_of_json; + public bool EndOfJson { get; private set; } - public JsonToken Token => token; + public JsonToken Token { get; private set; } - public object Value => token_value; + public object Value { get; private set; } #endregion @@ -112,8 +109,8 @@ namespace LitJson lexer = new Lexer (reader); - end_of_input = false; - end_of_json = false; + EndOfInput = false; + EndOfJson = false; this.reader = reader; reader_is_owned = owned; @@ -250,8 +247,8 @@ namespace LitJson double n_double; if (Double.TryParse (number, out n_double)) { - token = JsonToken.Double; - token_value = n_double; + Token = JsonToken.Double; + Value = n_double; return; } @@ -259,41 +256,41 @@ namespace LitJson int n_int32; if (Int32.TryParse (number, out n_int32)) { - token = JsonToken.Int; - token_value = n_int32; + Token = JsonToken.Int; + Value = n_int32; return; } long n_int64; if (Int64.TryParse (number, out n_int64)) { - token = JsonToken.Long; - token_value = n_int64; + Token = JsonToken.Long; + Value = n_int64; return; } // Shouldn't happen, but just in case, return something - token = JsonToken.Int; - token_value = 0; + Token = JsonToken.Int; + Value = 0; } private void ProcessSymbol () { if (current_symbol == '[') { - token = JsonToken.ArrayStart; + Token = JsonToken.ArrayStart; parser_return = true; } else if (current_symbol == ']') { - token = JsonToken.ArrayEnd; + Token = JsonToken.ArrayEnd; parser_return = true; } else if (current_symbol == '{') { - token = JsonToken.ObjectStart; + Token = JsonToken.ObjectStart; parser_return = true; } else if (current_symbol == '}') { - token = JsonToken.ObjectEnd; + Token = JsonToken.ObjectEnd; parser_return = true; } else if (current_symbol == '"') { @@ -303,22 +300,22 @@ namespace LitJson parser_return = true; } else { - if (token == JsonToken.None) - token = JsonToken.String; + if (Token == JsonToken.None) + Token = JsonToken.String; parser_in_string = true; } } else if (current_symbol == (int) ParserToken.CharSeq) { - token_value = lexer.StringValue; + Value = lexer.StringValue; } else if (current_symbol == (int) ParserToken.False) { - token = JsonToken.Boolean; - token_value = false; + Token = JsonToken.Boolean; + Value = false; parser_return = true; } else if (current_symbol == (int) ParserToken.Null) { - token = JsonToken.Null; + Token = JsonToken.Null; parser_return = true; } else if (current_symbol == (int) ParserToken.Number) { @@ -327,11 +324,11 @@ namespace LitJson parser_return = true; } else if (current_symbol == (int) ParserToken.Pair) { - token = JsonToken.PropertyName; + Token = JsonToken.PropertyName; } else if (current_symbol == (int) ParserToken.True) { - token = JsonToken.Boolean; - token_value = true; + Token = JsonToken.Boolean; + Value = true; parser_return = true; } @@ -339,7 +336,7 @@ namespace LitJson private bool ReadToken () { - if (end_of_input) + if (EndOfInput) return false; lexer.NextToken (); @@ -359,11 +356,11 @@ namespace LitJson public void Close () { - if (end_of_input) + if (EndOfInput) return; - end_of_input = true; - end_of_json = true; + EndOfInput = true; + EndOfJson = true; if (reader_is_owned) reader.Close (); @@ -373,11 +370,11 @@ namespace LitJson public bool Read () { - if (end_of_input) + if (EndOfInput) return false; - if (end_of_json) { - end_of_json = false; + if (EndOfJson) { + EndOfJson = false; automaton_stack.Clear (); automaton_stack.Push ((int) ParserToken.End); automaton_stack.Push ((int) ParserToken.Text); @@ -386,8 +383,8 @@ namespace LitJson parser_in_string = false; parser_return = false; - token = JsonToken.None; - token_value = null; + Token = JsonToken.None; + Value = null; if (! read_started) { read_started = true; @@ -400,7 +397,7 @@ namespace LitJson while (true) { if (parser_return) { if (automaton_stack.Peek () == (int) ParserToken.End) - end_of_json = true; + EndOfJson = true; return true; } diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/LLSD/BinaryLLSD.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/LLSD/BinaryLLSD.cs index 64c9412..78d1dd3 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/LLSD/BinaryLLSD.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/LLSD/BinaryLLSD.cs @@ -38,7 +38,6 @@ using System; using System.IO; -using System.Collections; using System.Collections.Generic; using System.Text; diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/LLSD/XmlLLSD.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/LLSD/XmlLLSD.cs index 00a80fd..9fe9d81 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/LLSD/XmlLLSD.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/LLSD/XmlLLSD.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -28,7 +29,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Xml; -using System.Xml.Schema; using System.Text; namespace OpenMetaverse.StructuredData @@ -38,46 +38,75 @@ namespace OpenMetaverse.StructuredData /// public static partial class OSDParser { - private static XmlSchema XmlSchema; - private static XmlTextReader XmlTextReader; - private static string LastXmlErrors = string.Empty; - private static object XmlValidationLock = new object(); + private static string linden_lab_loves_bad_pi = " - /// + /// Deserialize LLSD/XML stream /// - /// + /// a containing the serialized data + /// + public static OSD DeserializeLLSDXml(Stream xmlStream) + { + // XmlReader don't take no shit from nobody. Parse out Linden Lab's bad PI. + bool match = true; + for (int i = 0; i < linden_lab_loves_bad_pi.Length; ++i) + { + if (xmlStream.ReadByte() != linden_lab_loves_bad_pi[i]) + { + match = false; + break; + } + } + if (match) + { + // read until the linebreak > + while (xmlStream.ReadByte() != '\n') + { } + } else { + xmlStream.Seek(0, SeekOrigin.Begin); + } + + XmlReaderSettings settings = new XmlReaderSettings + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreComments = true, + IgnoreProcessingInstructions = false, + DtdProcessing = DtdProcessing.Ignore + }; + using (XmlReader xrd = XmlReader.Create(xmlStream)) + { + return DeserializeLLSDXml(xrd); + } + } + + /// + /// Deserialize LLSD/XML stream + /// + /// Data as a byte array /// public static OSD DeserializeLLSDXml(byte[] xmlData) { - using(XmlTextReader xrd = new XmlTextReader(new MemoryStream(xmlData, false))) - return DeserializeLLSDXml(xrd); - } - - public static OSD DeserializeLLSDXml(Stream xmlStream) - { - using(XmlTextReader xrd = new XmlTextReader(xmlStream)) - return DeserializeLLSDXml(xrd); + return DeserializeLLSDXml(new MemoryStream(xmlData, false)); } /// - /// + /// Deserialize LLSD/XML stream /// - /// + /// Serialized data as a /// public static OSD DeserializeLLSDXml(string xmlData) { byte[] bytes = Utils.StringToBytes(xmlData); - using(XmlTextReader xrd = new XmlTextReader(new MemoryStream(bytes, false))) - return DeserializeLLSDXml(xrd); + return DeserializeLLSDXml(bytes); } /// - /// + /// Deserialize LLSD/XML stream /// - /// + /// Serialized data as a /// - public static OSD DeserializeLLSDXml(XmlTextReader xmlData) + public static OSD DeserializeLLSDXml(XmlReader xmlData) { try { @@ -89,17 +118,18 @@ namespace OpenMetaverse.StructuredData return ret; } - catch + catch (XmlException ex) { + string exs = ex.ToString(); return new OSD(); } } /// - /// + /// Serialize an OSD object in LLSD/XML /// - /// - /// + /// OSD object to serialize + /// Serialized data as a byte aray public static byte[] SerializeLLSDXmlBytes(OSD data) { return Encoding.UTF8.GetBytes(SerializeLLSDXmlString(data)); @@ -113,7 +143,7 @@ namespace OpenMetaverse.StructuredData public static string SerializeLLSDXmlString(OSD data) { StringWriter sw = new StringWriter(); - using(XmlTextWriter writer = new XmlTextWriter(sw)) + using (XmlTextWriter writer = new XmlTextWriter(sw)) { writer.Formatting = Formatting.None; @@ -143,7 +173,7 @@ namespace OpenMetaverse.StructuredData /// /// /// - public static void SerializeLLSDXmlElement(XmlTextWriter writer, OSD data) + public static void SerializeLLSDXmlElement(XmlWriter writer, OSD data) { switch (data.Type) { @@ -224,59 +254,12 @@ namespace OpenMetaverse.StructuredData } } - /// - /// - /// - /// - /// - /// - public static bool TryValidateLLSDXml(XmlTextReader xmlData, out string error) - { - lock (XmlValidationLock) - { - LastXmlErrors = string.Empty; - XmlTextReader = xmlData; - - CreateLLSDXmlSchema(); - - XmlReaderSettings readerSettings = new XmlReaderSettings(); - readerSettings.ValidationType = ValidationType.Schema; - readerSettings.Schemas.Add(XmlSchema); - readerSettings.ValidationEventHandler += new ValidationEventHandler(LLSDXmlSchemaValidationHandler); - - using(XmlReader reader = XmlReader.Create(xmlData, readerSettings)) - { - - try - { - while (reader.Read()) { } - } - catch (XmlException) - { - error = LastXmlErrors; - return false; - } - - if (LastXmlErrors == string.Empty) - { - error = null; - return true; - } - else - { - error = LastXmlErrors; - return false; - } - } - } - } - /// /// /// /// /// - private static OSD ParseLLSDXmlElement(XmlTextReader reader) + private static OSD ParseLLSDXmlElement(XmlReader reader) { SkipWhitespace(reader); @@ -464,7 +447,7 @@ namespace OpenMetaverse.StructuredData return ret; } - private static OSDMap ParseLLSDXmlMap(XmlTextReader reader) + private static OSDMap ParseLLSDXmlMap(XmlReader reader) { if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map") throw new NotImplementedException("Expected "); @@ -507,7 +490,7 @@ namespace OpenMetaverse.StructuredData return map; } - private static OSDArray ParseLLSDXmlArray(XmlTextReader reader) + private static OSDArray ParseLLSDXmlArray(XmlReader reader) { if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array") throw new OSDException("Expected "); @@ -539,7 +522,7 @@ namespace OpenMetaverse.StructuredData return array; } - private static void SkipWhitespace(XmlTextReader reader) + private static void SkipWhitespace(XmlReader reader) { while ( reader.NodeType == XmlNodeType.Comment || @@ -550,121 +533,5 @@ namespace OpenMetaverse.StructuredData reader.Read(); } } - - private static void CreateLLSDXmlSchema() - { - if (XmlSchema == null) - { - #region XSD - const string schemaText = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - #endregion XSD - - MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(schemaText)); - - XmlSchema = new XmlSchema(); - XmlSchema = XmlSchema.Read(stream, new ValidationEventHandler(LLSDXmlSchemaValidationHandler)); - } - } - - private static void LLSDXmlSchemaValidationHandler(object sender, ValidationEventArgs args) - { - string error = - $"Line: {XmlTextReader.LineNumber} - Position: {XmlTextReader.LinePosition} - {args.Message}"; - - if (LastXmlErrors == string.Empty) - LastXmlErrors = error; - else - LastXmlErrors += Environment.NewLine + error; - } } } diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/StructuredData.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/StructuredData.cs index 4ba93bf..61a5a3c 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/StructuredData.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.StructuredData/StructuredData.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -892,6 +893,11 @@ namespace OpenMetaverse.StructuredData return new OSDMap(new Dictionary(_mMap)); } + public Hashtable ToHashtable() + { + return new Hashtable(_mMap); + } + #region IDictionary Implementation public int Count => _mMap.Count; @@ -1124,6 +1130,11 @@ namespace OpenMetaverse.StructuredData return OSDParser.SerializeJsonString(this, true); } + public ArrayList ToArrayList() + { + return new ArrayList(_mArray); + } + #region IList Implementation public int Count => _mArray.Count; @@ -1246,7 +1257,7 @@ namespace OpenMetaverse.StructuredData public static OSD Deserialize(Stream stream) { - if (!stream.CanSeek) throw new OSDException("Cannot deserialize structured data from unseekable streams"); + if (!stream.CanSeek) { throw new OSDException("Cannot deserialize structured data from unseekable streams"); } byte[] headerData = new byte[14]; stream.Read(headerData, 0, 14); diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/CRC32.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/CRC32.cs index eb32dee..8811889 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/CRC32.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/CRC32.cs @@ -24,8 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; - namespace OpenMetaverse { public class CRC32 diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Quaternion.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Quaternion.cs index f4ffdf0..1dae88a 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Quaternion.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Quaternion.cs @@ -321,6 +321,15 @@ namespace OpenMetaverse //} } + /// + /// Convert quaternion to euler angles vector + /// + /// + public Vector3 ToEulerVector() { + GetEulerAngles(out float r, out float p, out float y); + return new Vector3(r, p, y); + } + /// /// Convert this quaternion to an angle around an axis /// diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Ray.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Ray.cs new file mode 100644 index 0000000..18fd694 --- /dev/null +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Ray.cs @@ -0,0 +1,40 @@ +/* + * 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. + */ + +namespace OpenMetaverse +{ + public struct Ray + { + public Vector3 Origin; + public Vector3 Direction; + + public Ray(Vector3 origin, Vector3 direction) + { + Origin = origin; + Direction = direction; + } + } +} diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Ray.cs.meta b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Ray.cs.meta new file mode 100644 index 0000000..5832707 --- /dev/null +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/Ray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dfd7f6aa761fa8344ae0221058b55cf3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/TokenBucket.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/TokenBucket.cs index 2b1b381..3dab44a 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/TokenBucket.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/TokenBucket.cs @@ -55,10 +55,7 @@ namespace OpenMetaverse /// parent. The parent bucket will limit the aggregate bandwidth of all /// of its children buckets /// - public TokenBucket Parent - { - get { return parent; } - } + public TokenBucket Parent => parent; /// /// Maximum burst rate in bytes per second. This is the maximum number @@ -66,8 +63,8 @@ namespace OpenMetaverse /// public int MaxBurst { - get { return maxBurst; } - set { maxBurst = (value >= 0 ? value : 0); } + get => maxBurst; + set => maxBurst = (value >= 0 ? value : 0); } /// @@ -75,11 +72,11 @@ namespace OpenMetaverse /// number of tokens that are added to the bucket per second /// /// Tokens are added to the bucket any time - /// is called, at the granularity of + /// is called, at the granularity of /// the system tick interval (typically around 15-22ms) public int DripRate { - get { return tokensPerMS * 1000; } + get => tokensPerMS * 1000; set { if (value == 0) @@ -95,13 +92,10 @@ namespace OpenMetaverse /// The number of bytes that can be sent at this moment. This is the /// current number of tokens in the bucket /// If this bucket has a parent bucket that does not have - /// enough tokens for a request, will + /// enough tokens for a request, will /// return false regardless of the content of this bucket /// - public int Content - { - get { return content; } - } + public int Content => content; #endregion Properties diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/UUID.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/UUID.cs index d6bf43b..91ac438 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/UUID.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/UUID.cs @@ -297,9 +297,8 @@ namespace OpenMetaverse /// True if the object is a UUID and both UUIDs are equal public override bool Equals(object o) { - if (!(o is UUID)) return false; + if (!(o is UUID uuid)) return false; - UUID uuid = (UUID)o; return Guid == uuid.Guid; } diff --git a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/UtilsConversions.cs b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/UtilsConversions.cs index 0211354..22bdeb3 100644 --- a/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/UtilsConversions.cs +++ b/Assets/Plugins/LibreMetaverse/LibreMetaverse.Types/UtilsConversions.cs @@ -25,6 +25,7 @@ */ using System; +using System.Collections; using System.Linq; using System.Net; using System.Text; @@ -654,7 +655,7 @@ namespace OpenMetaverse /// 0x7fffffff public static string UIntToHexString(uint i) { - return string.Format("{0:x8}", i); + return $"{i:x8}"; } /// @@ -680,7 +681,7 @@ namespace OpenMetaverse private static string GetString(byte[] bytes, int index, int count) { - string cnv = UTF8Encoding.UTF8.GetString(bytes, index, count); + string cnv = Encoding.UTF8.GetString(bytes, index, count); return InternStrings ? string.Intern(cnv) : cnv; } @@ -729,7 +730,8 @@ namespace OpenMetaverse if (j != 0) output.Append(' '); - output.Append(String.Format("{0:X2}", bytes[i + j])); + //output.Append(String.Format("{0:X2}", bytes[i + j])); + output.Append($"{bytes[i + j]:X2}"); } } } @@ -744,9 +746,11 @@ namespace OpenMetaverse /// A null-terminated UTF8 byte array public static byte[] StringToBytes(string str) { - if (String.IsNullOrEmpty(str)) { return EmptyBytes; } - if (!str.EndsWith("\0")) { str += "\0"; } - return UTF8Encoding.UTF8.GetBytes(str); + if (string.IsNullOrEmpty(str)) { return EmptyBytes; } + // HACK: Say it ain't so .NET5 + return str.EndsWith("\0", StringComparison.Ordinal) + ? Encoding.UTF8.GetBytes(str) + : Encoding.UTF8.GetBytes(str + '\0'); } /// @@ -767,9 +771,9 @@ namespace OpenMetaverse char c; // remove all non A-F, 0-9, characters - for (int i = 0; i < hexString.Length; i++) + foreach (var str in hexString) { - c = hexString[i]; + c = str; if (IsHexDigit(c)) stripped.Append(c); } @@ -801,14 +805,14 @@ namespace OpenMetaverse /// /// Character to test /// true if hex digit, false if not - private static bool IsHexDigit(Char c) + private static bool IsHexDigit(char c) { const int numA = 65; const int num0 = 48; int numChar; - c = Char.ToUpper(c); + c = char.ToUpper(c); numChar = Convert.ToInt32(c); if (numChar >= numA && numChar < (numA + 6)) @@ -1217,9 +1221,7 @@ namespace OpenMetaverse /// Second value public static void Swap(ref T lhs, ref T rhs) { - T temp = lhs; - lhs = rhs; - rhs = temp; + (lhs, rhs) = (rhs, lhs); } /// diff --git a/Assets/Plugins/LibreMetaverse/LocationParser.cs b/Assets/Plugins/LibreMetaverse/LocationParser.cs new file mode 100644 index 0000000..8592489 --- /dev/null +++ b/Assets/Plugins/LibreMetaverse/LocationParser.cs @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022, Sjofn, LLC + * 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; + +namespace OpenMetaverse +{ + public class LocationParser + { + public string Sim { get; } + public int X { get; } + public int Y { get; } + public int Z { get; } + + public LocationParser(string location) + { + if (location == null ) { throw new ArgumentNullException("Location cannot be null."); } + if (location.Length == 0) { throw new ArgumentException("Location cannot be empty."); } + + string toParse; + if (location.StartsWith("secondlife://")) { toParse = location.Substring(13); } + else if (location.StartsWith("uri:")) { toParse = location.Substring(4); } + else { toParse = location; } + + string[] elements = toParse.Split('/'); + Sim = elements[0]; + int parsed; + X = (elements.Length > 1 && int.TryParse(elements[1], out parsed)) ? parsed : 128; + Y = (elements.Length > 2 && int.TryParse(elements[2], out parsed)) ? parsed : 128; + Z = (elements.Length > 3 && int.TryParse(elements[3], out parsed)) ? parsed : 0; + } + + public string GetRawLocation() + { + return $"{Sim}/{X}/{Y}/{Z}"; + } + + public string GetSlurl() + { + return $"secondlife://{Sim}/{X}/{Y}/{Z}/"; + } + + public string GetStartLocationUri() + { + return $"uri:{Sim}&{X}&{Y}&{Z}"; + } + } +} diff --git a/Assets/Plugins/LibreMetaverse/LocationParser.cs.meta b/Assets/Plugins/LibreMetaverse/LocationParser.cs.meta new file mode 100644 index 0000000..3abf2e9 --- /dev/null +++ b/Assets/Plugins/LibreMetaverse/LocationParser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c55f03914703744682d029a8cfbceda +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/LibreMetaverse/Login.cs b/Assets/Plugins/LibreMetaverse/Login.cs index 3e8cecc..782b269 100644 --- a/Assets/Plugins/LibreMetaverse/Login.cs +++ b/Assets/Plugins/LibreMetaverse/Login.cs @@ -1,6 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co - * Copyright (c) 2019-2020, Sjofn, LLC + * Copyright (c) 2019-2022, Sjofn, LLC * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -35,7 +35,6 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.NetworkInformation; using System.Security.Cryptography; using System.Text; using System.Threading; @@ -151,6 +150,10 @@ namespace OpenMetaverse /// internally in the library and is never sent over the wire internal UUID LoginID; + /// LoginLocation used to set the starting region and location (overrides Start) example: "Tentacles/128/64/109" + /// Leave empty to use the starting location + public string LoginLocation; + /// /// Default constructor, initializes sane default values /// @@ -220,7 +223,8 @@ namespace OpenMetaverse /// Password /// Login channel (application name) /// Client version, should be application name + version number - public LoginParams(GridClient client, string firstName, string lastName, string password, string channel, string version) + public LoginParams(GridClient client, string firstName, string lastName, string password, + string channel, string version) : this() { URI = client.Settings.LOGIN_SERVER; @@ -239,10 +243,11 @@ namespace OpenMetaverse /// Login first name /// Login last name /// Password - /// Login channnel (application name) + /// Login channel (application name) /// Client version, should be application name + version number /// URI of the login server - public LoginParams(GridClient client, string firstName, string lastName, string password, string channel, string version, string loginURI) + public LoginParams(GridClient client, string firstName, string lastName, string password, + string channel, string version, string loginURI) : this(client, firstName, lastName, password, channel, version) { URI = loginURI; @@ -256,6 +261,13 @@ namespace OpenMetaverse public int BuddyRightsHas; } + public struct HomeInfo + { + public ulong RegionHandle; + public Vector3 Position; + public Vector3 LookAt; + } + /// /// The decoded data returned from the login server after a successful login /// @@ -268,18 +280,21 @@ namespace OpenMetaverse public string Reason; /// Login message of the day public string Message; + public bool FirstLogin; public UUID AgentID; public UUID SessionID; public UUID SecureSessionID; public string FirstName; public string LastName; public string StartLocation; + public string AccountType; /// M or PG, also agent_region_access and agent_access_max public string AgentAccess; + public string AgentAccessMax; + public string AgentRegionAccess; + public string InitialOutfit; public Vector3 LookAt; - public ulong HomeRegion; - public Vector3 HomePosition; - public Vector3 HomeLookAt; + public HomeInfo Home; public int CircuitCode; public uint RegionX; public uint RegionY; @@ -289,6 +304,18 @@ namespace OpenMetaverse public BuddyListEntry[] BuddyList; public int SecondsSinceEpoch; public string UDPBlacklist; + public int MaxAgentGroups; + public string OpenIDUrl; + public string AgentAppearanceServiceURL; + public string MapServerUrl; + public string SnapshotConfigUrl; + public uint COFVersion; + public Hashtable AccountLevelBenefits; + public Hashtable PremiumPackages; + public ArrayList ClassifiedCategories; + public ArrayList EventCategories; + public ArrayList GlobalTextures; + public ArrayList UiConfig; #region Inventory @@ -297,6 +324,7 @@ namespace OpenMetaverse public InventoryFolder[] InventorySkeleton; public InventoryFolder[] LibrarySkeleton; public UUID LibraryOwner; + public Dictionary Gestures; #endregion @@ -309,19 +337,6 @@ namespace OpenMetaverse #endregion - // These aren't currently being utilized by the library - public string AgentAccessMax; - public string AgentRegionAccess; - public int AOTransition; - public string InventoryHost; - public int MaxAgentGroups; - public string OpenIDUrl; - public string AgentAppearanceServiceURL; - public uint COFVersion; - public string InitialOutfit; - public bool FirstLogin; - public Dictionary Gestures; - /// /// Parse LLSD Login Reply Data /// @@ -336,10 +351,12 @@ namespace OpenMetaverse AgentID = ParseUUID("agent_id", reply); SessionID = ParseUUID("session_id", reply); SecureSessionID = ParseUUID("secure_session_id", reply); - FirstName = ParseString("first_name", reply).Trim('"'); - LastName = ParseString("last_name", reply).Trim('"'); + FirstName = ParseString("first_name", reply).Trim('"').Trim(); // lol but necessary, unfortunately. + LastName = ParseString("last_name", reply).Trim('"').Trim(); StartLocation = ParseString("start_location", reply); AgentAccess = ParseString("agent_access", reply); + AgentAccessMax = ParseString("agent_access_max", reply); + AgentRegionAccess = ParseString("agent_region_access", reply); LookAt = ParseVector3("look_at", reply); Reason = ParseString("reason", reply); Message = ParseString("message", reply); @@ -349,40 +366,59 @@ namespace OpenMetaverse } catch (OSDException e) { - Logger.Log("Login server returned (some) invalid data: " + e.Message, Helpers.LogLevel.Warning); + Logger.Log("Login server returned (some) invalid data", Helpers.LogLevel.Warning, e); } // Home - OSD osdHome = OSDParser.DeserializeLLSDNotation(reply["home"].AsString()); - - if (osdHome.Type == OSDType.Map) + if (reply.ContainsKey("home_info")) { - var home = (OSDMap)osdHome; - - OSD homeRegion; - if (home.TryGetValue("region_handle", out homeRegion) && homeRegion.Type == OSDType.Array) + if (reply["home_info"].Type == OSDType.Map) { - OSDArray homeArray = (OSDArray)homeRegion; - HomeRegion = homeArray.Count == 2 - ? Utils.UIntsToLong((uint)homeArray[0].AsInteger(), (uint)homeArray[1].AsInteger()) - : 0; - } + var map = (OSDMap)reply["home_info"]; + Home.Position = ParseVector3("position", map); + Home.LookAt = ParseVector3("look_at", map); - HomePosition = ParseVector3("position", home); - HomeLookAt = ParseVector3("look_at", home); + var coords = (OSDArray)OSDParser.DeserializeLLSDNotation(map["region_handle"].ToString()); + if (coords.Type == OSDType.Array) + { + Home.RegionHandle = (coords.Count == 2) + ? Utils.UIntsToLong((uint)coords[0].AsInteger(), (uint)coords[1].AsInteger()) : 0; + } + } + } + else if (reply.ContainsKey("home")) + { + var osdHome = OSDParser.DeserializeLLSDNotation(reply["home"].AsString()); + + if (osdHome.Type == OSDType.Map) + { + var home = (OSDMap)osdHome; + + OSD homeRegion; + if (home.TryGetValue("region_handle", out homeRegion) && homeRegion.Type == OSDType.Array) + { + var homeArray = (OSDArray)homeRegion; + Home.RegionHandle = homeArray.Count == 2 + ? Utils.UIntsToLong((uint)homeArray[0].AsInteger(), (uint)homeArray[1].AsInteger()) + : 0; + } + + Home.Position = ParseVector3("position", home); + Home.LookAt = ParseVector3("look_at", home); + } } else { - HomeRegion = 0; - HomePosition = Vector3.Zero; - HomeLookAt = Vector3.Zero; + Home.RegionHandle = 0; + Home.Position = Vector3.Zero; + Home.LookAt = Vector3.Zero; } CircuitCode = (int)ParseUInt("circuit_code", reply); RegionX = ParseUInt("region_x", reply); RegionY = ParseUInt("region_y", reply); SimPort = (ushort)ParseUInt("sim_port", reply); - string simIP = ParseString("sim_ip", reply); + var simIP = ParseString("sim_ip", reply); IPAddress.TryParse(simIP, out SimIP); SeedCapability = ParseString("seed_capability", reply); @@ -390,14 +426,14 @@ namespace OpenMetaverse OSD buddyLLSD; if (reply.TryGetValue("buddy-list", out buddyLLSD) && buddyLLSD.Type == OSDType.Array) { - List buddys = new List(); - OSDArray buddyArray = (OSDArray)buddyLLSD; - foreach (OSD t in buddyArray) + var buddys = new List(); + var buddyArray = (OSDArray)buddyLLSD; + foreach (var t in buddyArray) { if (t.Type == OSDType.Map) { - BuddyListEntry bud = new BuddyListEntry(); - OSDMap buddy = (OSDMap)t; + var bud = new BuddyListEntry(); + var buddy = (OSDMap)t; bud.BuddyId = buddy["buddy_id"].AsString(); bud.BuddyRightsGiven = (int)ParseUInt("buddy_rights_given", buddy); @@ -417,6 +453,63 @@ namespace OpenMetaverse LibraryOwner = ParseMappedUUID("inventory-lib-owner", "agent_id", reply); LibraryRoot = ParseMappedUUID("inventory-lib-root", "folder_id", reply); LibrarySkeleton = ParseInventorySkeleton("inventory-skel-lib", reply); + + if (reply.ContainsKey("account_level_benefits")) + { + if (reply["account_level_benefits"].Type == OSDType.Map) + { + AccountLevelBenefits = ((OSDMap)reply["account_level_benefits"]).ToHashtable(); + } + } + + if (reply.ContainsKey("classified_categories")) + { + if (reply["classified_categories"].Type == OSDType.Array) + { + ClassifiedCategories = ((OSDArray)reply["classified_categories"]).ToArrayList(); + } + } + + if (reply.ContainsKey("event_categories")) + { + if (reply["event_categories"].Type == OSDType.Array) + { + EventCategories = ((OSDArray)reply["event_categories"]).ToArrayList(); + } + } + + if (reply.ContainsKey("global-textures")) + { + if (reply["global-textures"].Type == OSDType.Array) + { + GlobalTextures = ((OSDArray)reply["global-textures"]).ToArrayList(); + } + } + + if (reply.ContainsKey("premium_packages")) + { + if (reply["premium_packages"].Type == OSDType.Map) + { + PremiumPackages = ((OSDMap)reply["premium_packages"]).ToHashtable(); + } + } + + if (reply.ContainsKey("ui-config")) + { + if (reply["ui-config"].Type == OSDType.Array) + { + UiConfig = ((OSDArray)reply["ui-config"]).ToArrayList(); + } + } + + if (reply.ContainsKey("max-agent-groups")) + { + MaxAgentGroups = (int)ParseUInt("max-agent-groups", reply); + } + else + { + MaxAgentGroups = -1; + } } public void Parse(Hashtable reply) @@ -454,45 +547,78 @@ namespace OpenMetaverse { Logger.Log("Login server returned (some) invalid data: " + e.Message, Helpers.LogLevel.Warning); } - if (!Success) - return; + if (!Success) { return; } - // Home - if (reply.ContainsKey("home")) + // HomeInfo + try { - OSD osdHome = OSDParser.DeserializeLLSDNotation(reply["home"].ToString()); - - if (osdHome.Type == OSDType.Map) + if (reply.ContainsKey("home_info")) { - var home = (OSDMap)osdHome; - - OSD homeRegion; - if (home.TryGetValue("region_handle", out homeRegion) && homeRegion.Type == OSDType.Array) + if (reply?["home_info"] is Hashtable map) { - OSDArray homeArray = (OSDArray)homeRegion; - if (homeArray.Count == 2) - HomeRegion = Utils.UIntsToLong((uint)homeArray[0].AsInteger(), - (uint)homeArray[1].AsInteger()); - else - HomeRegion = 0; - } + Home.Position = ParseVector3("position", map); + Home.LookAt = ParseVector3("look_at", map); - HomePosition = ParseVector3("position", home); - HomeLookAt = ParseVector3("look_at", home); + var coords = (OSDArray)OSDParser.DeserializeLLSDNotation(map["region_handle"].ToString()); + if (coords.Type == OSDType.Array) + { + Home.RegionHandle = (coords.Count == 2) + ? Utils.UIntsToLong((uint)coords[0].AsInteger(), (uint)coords[1].AsInteger()) : 0; + } + } } - } - else + + // Home + if (Home.RegionHandle == 0 && reply.ContainsKey("home")) + { + if (reply?["home"] is Hashtable map) + { + Home.Position = ParseVector3("position", map); + Home.LookAt = ParseVector3("look_at", map); + + var coords = (OSDArray)OSDParser.DeserializeLLSDNotation(map["region_handle"].ToString()); + if (coords.Type == OSDType.Array) + { + Home.RegionHandle = (coords.Count == 2) + ? Utils.UIntsToLong((uint)coords[0].AsInteger(), (uint)coords[1].AsInteger()) : 0; + } + } + else if (reply?["home"] is string osdString) + { + var osdHome = OSDParser.DeserializeLLSDNotation(reply["home"].ToString()); + + if (osdHome.Type == OSDType.Map) + { + var home = (OSDMap)osdHome; + + OSD homeRegion; + if (home.TryGetValue("region_handle", out homeRegion) && homeRegion.Type == OSDType.Array) + { + var coords = (OSDArray)homeRegion; + Home.RegionHandle = (coords.Count == 2) + ? Utils.UIntsToLong((uint)coords[0].AsInteger(), (uint)coords[1].AsInteger()) : 0; + + } + Home.Position = ParseVector3("position", home); + Home.LookAt = ParseVector3("look_at", home); + } + } + else + { + throw new Exception("Could not parse 'home' in Login Response"); + } + } + } catch (Exception ex) { - HomeRegion = 0; - HomePosition = Vector3.Zero; - HomeLookAt = Vector3.Zero; + Logger.Log("Could not parse home info from login response. Setting nil", Helpers.LogLevel.Warning, ex); + Home = new HomeInfo(); } CircuitCode = (int)ParseUInt("circuit_code", reply); RegionX = ParseUInt("region_x", reply); RegionY = ParseUInt("region_y", reply); SimPort = (ushort)ParseUInt("sim_port", reply); - string simIP = ParseString("sim_ip", reply); + var simIP = ParseString("sim_ip", reply); IPAddress.TryParse(simIP, out SimIP); SeedCapability = ParseString("seed_capability", reply); @@ -504,14 +630,14 @@ namespace OpenMetaverse var buddyArray = (ArrayList)reply["buddy-list"]; foreach (var t in buddyArray) { - if (!(t is Hashtable)) continue; + if (!(t is Hashtable buddy)) continue; - var bud = new BuddyListEntry(); - var buddy = (Hashtable)t; - - bud.BuddyId = ParseString("buddy_id", buddy); - bud.BuddyRightsGiven = (int)ParseUInt("buddy_rights_given", buddy); - bud.BuddyRightsHas = (int)ParseUInt("buddy_rights_has", buddy); + var bud = new BuddyListEntry + { + BuddyId = ParseString("buddy_id", buddy), + BuddyRightsGiven = (int)ParseUInt("buddy_rights_given", buddy), + BuddyRightsHas = (int)ParseUInt("buddy_rights_has", buddy) + }; buddys.Add(bud); } @@ -527,11 +653,61 @@ namespace OpenMetaverse LibraryOwner = ParseMappedUUID("inventory-lib-owner", "agent_id", reply); LibraryRoot = ParseMappedUUID("inventory-lib-root", "folder_id", reply); LibrarySkeleton = ParseInventorySkeleton("inventory-skel-lib", reply); + + AccountType = ParseString("account_type", reply); + AgentAppearanceServiceURL = ParseString("agent_appearance_service", reply); + COFVersion = ParseUInt("cof_version", reply); + MapServerUrl = ParseString("map-server-url", reply); + OpenIDUrl = ParseString("openid_url", reply); + SnapshotConfigUrl = ParseString("snapshot_config_url", reply); + UDPBlacklist = ParseString("udp_blacklist", reply); - // UDP Blacklist - if (reply.ContainsKey("udp_blacklist")) + if (reply.ContainsKey("account_level_benefits")) { - UDPBlacklist = ParseString("udp_blacklist", reply); + if (reply?["account_level_benefits"] is Hashtable) + { + AccountLevelBenefits = (Hashtable)reply["account_level_benefits"]; + } + } + + if (reply.ContainsKey("classified_categories")) + { + if (reply?["classified_categories"] is ArrayList) + { + ClassifiedCategories = (ArrayList)reply["classified_categories"]; + } + } + + if (reply.ContainsKey("event_categories")) + { + if (reply?["event_categories"] is ArrayList) + { + EventCategories = (ArrayList)reply["event_categories"]; + } + } + + if (reply.ContainsKey("global-textures")) + { + if (reply?["global-textures"] is ArrayList) + { + GlobalTextures = (ArrayList)reply["global-textures"]; + } + } + + if (reply.ContainsKey("premium_packages")) + { + if (reply?["premium_packages"] is Hashtable) + { + PremiumPackages = (Hashtable)reply["premium_packages"]; + } + } + + if (reply.ContainsKey("ui-config")) + { + if (reply?["ui-config"] is ArrayList) + { + UiConfig = (ArrayList)reply["ui-config"]; + } } if (reply.ContainsKey("max-agent-groups")) @@ -543,21 +719,6 @@ namespace OpenMetaverse MaxAgentGroups = -1; } - if (reply.ContainsKey("openid_url")) - { - OpenIDUrl = ParseString("openid_url", reply); - } - - if (reply.ContainsKey("agent_appearance_service")) - { - AgentAppearanceServiceURL = ParseString("agent_appearance_service", reply); - } - - COFVersion = 0; - if (reply.ContainsKey("cof_version")) - { - COFVersion = ParseUInt("cof_version", reply); - } InitialOutfit = string.Empty; if (reply.ContainsKey("initial-outfit") && reply["initial-outfit"] is ArrayList) @@ -565,9 +726,8 @@ namespace OpenMetaverse var array = (ArrayList)reply["initial-outfit"]; foreach (var t in array) { - if (!(t is Hashtable)) continue; + if (!(t is Hashtable map)) continue; - var map = (Hashtable)t; InitialOutfit = ParseString("folder_name", map); } } @@ -605,9 +765,8 @@ namespace OpenMetaverse var array = (ArrayList)reply["login-flags"]; foreach (var t in array) { - if (!(t is Hashtable)) continue; + if (!(t is Hashtable map)) continue; - var map = (Hashtable)t; FirstLogin = ParseString("ever_logged_in", map) == "N"; } } @@ -651,12 +810,12 @@ namespace OpenMetaverse public static string ParseString(string key, OSDMap reply) { OSD osd; - return reply.TryGetValue(key, out osd) ? osd.AsString() : String.Empty; + return reply.TryGetValue(key, out osd) ? osd.AsString() : string.Empty; } public static string ParseString(string key, Hashtable reply) { - return reply.ContainsKey(key) ? $"{reply[key]}" : String.Empty; + return reply.ContainsKey(key) ? $"{reply[key]}" : string.Empty; } public static Vector3 ParseVector3(string key, OSDMap reply) @@ -669,7 +828,7 @@ namespace OpenMetaverse case OSDType.Array: return ((OSDArray)osd).AsVector3(); case OSDType.String: - OSDArray array = (OSDArray)OSDParser.DeserializeLLSDNotation(osd.AsString()); + var array = (OSDArray)OSDParser.DeserializeLLSDNotation(osd.AsString()); return array.AsVector3(); } @@ -687,16 +846,16 @@ namespace OpenMetaverse if (list.Count == 3) { float x, y, z; - Single.TryParse((string)list[0], out x); - Single.TryParse((string)list[1], out y); - Single.TryParse((string)list[2], out z); + float.TryParse((string)list[0], out x); + float.TryParse((string)list[1], out y); + float.TryParse((string)list[2], out z); return new Vector3(x, y, z); } } else if (value is string str) { - OSDArray array = (OSDArray)OSDParser.DeserializeLLSDNotation(str); + var array = (OSDArray)OSDParser.DeserializeLLSDNotation(str); return array.AsVector3(); } @@ -706,12 +865,12 @@ namespace OpenMetaverse public static UUID ParseMappedUUID(string key, string key2, OSDMap reply) { OSD folderOSD; - if (!reply.TryGetValue(key, out folderOSD) || folderOSD.Type != OSDType.Array) { return UUID.Zero; } + if (!reply.TryGetValue(key, out folderOSD) || folderOSD.Type != OSDType.Array) {return UUID.Zero;} - OSDArray array = (OSDArray)folderOSD; + var array = (OSDArray)folderOSD; if (array.Count == 1 && array[0].Type == OSDType.Map) { - OSDMap map = (OSDMap)array[0]; + var map = (OSDMap)array[0]; OSD folder; if (map.TryGetValue(key2, out folder)) return folder.AsUUID(); @@ -722,7 +881,7 @@ namespace OpenMetaverse public static UUID ParseMappedUUID(string key, string key2, Hashtable reply) { - if (!reply.ContainsKey(key) || !(reply[key] is ArrayList)) { return UUID.Zero; } + if (!reply.ContainsKey(key) || !(reply[key] is ArrayList)) {return UUID.Zero;} var array = (ArrayList)reply[key]; if (array.Count == 1 && array[0] is Hashtable) @@ -736,25 +895,25 @@ namespace OpenMetaverse public static InventoryFolder[] ParseInventoryFolders(string key, UUID owner, OSDMap reply) { - List folders = new List(); + var folders = new List(); OSD skeleton; - if (!reply.TryGetValue(key, out skeleton) || skeleton.Type != OSDType.Array) { return folders.ToArray(); } + if (!reply.TryGetValue(key, out skeleton) || skeleton.Type != OSDType.Array) {return folders.ToArray();} - OSDArray array = (OSDArray)skeleton; + var array = (OSDArray)skeleton; folders.AddRange(from t in array - where t.Type == OSDType.Map - select (OSDMap)t + where t.Type == OSDType.Map + select (OSDMap)t into map - select new InventoryFolder(map["folder_id"].AsUUID()) - { - PreferredType = (FolderType)map["type_default"].AsInteger(), - Version = map["version"].AsInteger(), - OwnerID = owner, - ParentUUID = map["parent_id"].AsUUID(), - Name = map["name"].AsString() - }); + select new InventoryFolder(map["folder_id"].AsUUID()) + { + PreferredType = (FolderType)map["type_default"].AsInteger(), + Version = map["version"].AsInteger(), + OwnerID = owner, + ParentUUID = map["parent_id"].AsUUID(), + Name = map["name"].AsString() + }); return folders.ToArray(); } @@ -764,19 +923,19 @@ namespace OpenMetaverse var folders = new List(); OSD skeleton; - if (!reply.TryGetValue(key, out skeleton) || skeleton.Type != OSDType.Array) { return folders.ToArray(); } - OSDArray array = (OSDArray)skeleton; + if (!reply.TryGetValue(key, out skeleton) || skeleton.Type != OSDType.Array) {return folders.ToArray();} + var array = (OSDArray)skeleton; folders.AddRange(from t in array - where t.Type == OSDType.Map - select (OSDMap)t + where t.Type == OSDType.Map + select (OSDMap)t into map - select new InventoryFolder(map["folder_id"].AsUUID()) - { - Name = map["name"].AsString(), - ParentUUID = map["parent_id"].AsUUID(), - PreferredType = (FolderType)map["type_default"].AsInteger(), - Version = map["version"].AsInteger() - }); + select new InventoryFolder(map["folder_id"].AsUUID()) + { + Name = map["name"].AsString(), + ParentUUID = map["parent_id"].AsUUID(), + PreferredType = (FolderType)map["type_default"].AsInteger(), + Version = map["version"].AsInteger() + }); return folders.ToArray(); } @@ -787,14 +946,13 @@ namespace OpenMetaverse var folders = new List(); - if (!reply.ContainsKey(key) || !(reply[key] is ArrayList)) { return folders.ToArray(); } + if (!reply.ContainsKey(key) || !(reply[key] is ArrayList)) {return folders.ToArray();} - ArrayList array = (ArrayList)reply[key]; - foreach (object t in array) + var array = (ArrayList)reply[key]; + foreach (var t in array) { - if (!(t is Hashtable)) continue; - Hashtable map = (Hashtable)t; - InventoryFolder folder = new InventoryFolder(ParseUUID("folder_id", map)) + if (!(t is Hashtable map)) continue; + var folder = new InventoryFolder(ParseUUID("folder_id", map)) { Name = ParseString("name", map), ParentUUID = ParseUUID("parent_id", map), @@ -893,9 +1051,9 @@ namespace OpenMetaverse #region Public Members /// Seed CAPS URL returned from the login server - public string LoginSeedCapability = String.Empty; + public string LoginSeedCapability = string.Empty; /// Current state of logging in - public LoginStatus LoginStatusCode => InternalStatusCode; + public LoginStatus LoginStatusCode { get; private set; } = LoginStatus.None; /// Upon login failure, contains a short string key for the /// type of login error that occurred @@ -923,13 +1081,13 @@ namespace OpenMetaverse public static readonly HttpClient HTTP_CLIENT = new HttpClient(new HttpClientHandler() { + //todo add the crt //ServerCertificateCustomValidationCallback = delegate { return true; }, AllowAutoRedirect = true }); - + private LoginParams CurrentContext = null; private readonly AutoResetEvent LoginEvent = new AutoResetEvent(false); - private LoginStatus InternalStatusCode = LoginStatus.None; private readonly Dictionary CallbackOptions = new Dictionary(); /// A list of packets obtained during the login process which @@ -990,7 +1148,7 @@ namespace OpenMetaverse public bool Login(string firstName, string lastName, string password, string channel, string start, string version) { - LoginParams loginParams = DefaultLoginParams(firstName, lastName, password, channel, version); + var loginParams = DefaultLoginParams(firstName, lastName, password, channel, version); loginParams.Start = start; return Login(loginParams); @@ -1014,18 +1172,18 @@ namespace OpenMetaverse if (CurrentContext != null) { CurrentContext = null; // Will force any pending callbacks to bail out early - InternalStatusCode = LoginStatus.Failed; + LoginStatusCode = LoginStatus.Failed; LoginMessage = "Timed out"; return false; } - return (InternalStatusCode == LoginStatus.Success); + return (LoginStatusCode == LoginStatus.Success); } public void BeginLogin(LoginParams loginParams) { // FIXME: Now that we're using CAPS we could cancel the current login and start a new one - if (CurrentContext != null) { throw new Exception("Login already in progress"); } + if (CurrentContext != null) {throw new Exception("Login already in progress");} LoginEvent.Reset(); CurrentContext = loginParams; @@ -1058,15 +1216,14 @@ namespace OpenMetaverse /// X coordinate to start at /// Y coordinate to start at /// Z coordinate to start at - /// String with a URI that can be used to login to a specified - /// location + /// String with a URI that can be used to login to a specified location public static string StartLocation(string sim, int x, int y, int z) { return $"uri:{sim}&{x}&{y}&{z}"; } public void AbortLogin() { - LoginParams loginParams = CurrentContext; + var loginParams = CurrentContext; CurrentContext = null; // Will force any pending callbacks to bail out early // FIXME: Now that we're using CAPS we could cancel the current login and start a new one if (loginParams == null) @@ -1075,7 +1232,7 @@ namespace OpenMetaverse } else { - InternalStatusCode = LoginStatus.Failed; + LoginStatusCode = LoginStatus.Failed; LoginMessage = "Aborted"; } UpdateLoginStatus(LoginStatus.Failed, "Abort Requested"); @@ -1084,10 +1241,10 @@ namespace OpenMetaverse #endregion #region Private Methods - // actually perform the login. + private void BeginLogin() { - LoginParams loginParams = CurrentContext; + var loginParams = CurrentContext; // Generate a random ID to identify this login attempt loginParams.LoginID = UUID.Random(); CurrentContext = loginParams; @@ -1100,7 +1257,7 @@ namespace OpenMetaverse if (loginParams.Password == null) loginParams.Password = string.Empty; - // Convert the password to MD5 if it isn't already + // *HACK: Convert the password to MD5 if it isn't already if (loginParams.Password.Length != 35 && !loginParams.Password.StartsWith("$1$")) loginParams.Password = Utils.MD5(loginParams.Password); @@ -1111,7 +1268,7 @@ namespace OpenMetaverse loginParams.Version = string.Empty; if (loginParams.UserAgent == null) - loginParams.UserAgent = string.Empty; + loginParams.UserAgent = Settings.USER_AGENT; if (loginParams.Platform == null) loginParams.Platform = string.Empty; @@ -1124,18 +1281,38 @@ namespace OpenMetaverse if (string.IsNullOrEmpty(loginParams.Channel)) { - Logger.Log("Viewer channel not set. This is a TOS violation on some grids.", + Logger.Log("Viewer channel not set.", Helpers.LogLevel.Warning); - loginParams.Channel = "LibreMetaverse client"; + loginParams.Channel = $"{Settings.USER_AGENT}"; + } + + if (!string.IsNullOrEmpty(loginParams.LoginLocation)) + { + var startLoc = new LocationParser(loginParams.LoginLocation.Trim()); + loginParams.Start = startLoc.GetStartLocationUri(); + } + else + { + switch (loginParams.Start) + { + case "home": + case "last": + break; + default: + var startLoc = new LocationParser(loginParams.Start.Trim()); + loginParams.Start = startLoc.GetStartLocationUri(); + break; + } } if (loginParams.Author == null) + { loginParams.Author = string.Empty; - + } #endregion // TODO: Allow a user callback to be defined for handling the cert - ServicePointManager.ServerCertificateValidationCallback = + ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; if (Client.Settings.USE_LLSD_LOGIN) @@ -1143,7 +1320,7 @@ namespace OpenMetaverse #region LLSD Based Login // Create the CAPS login structure - OSDMap loginLLSD = new OSDMap + var loginLLSD = new OSDMap { ["first"] = OSD.FromString(loginParams.FirstName), ["last"] = OSD.FromString(loginParams.LastName), @@ -1162,11 +1339,18 @@ namespace OpenMetaverse }; // Create the options LLSD array - OSDArray optionsOSD = new OSDArray(); + var optionsOSD = new OSDArray(); foreach (var t in loginParams.Options) optionsOSD.Add(OSD.FromString(t)); - foreach (var callbackOpts in CallbackOptions.Values) + foreach (var t in from callbackOpts in CallbackOptions.Values where callbackOpts != null + from t in callbackOpts where !optionsOSD.Contains(t) select t) + { + optionsOSD.Add(t); + } + + /* old version; i can't read the new one. + foreach (var callbackOpts in CallbackOptions.Values) { if (callbackOpts == null) continue; foreach (var t in callbackOpts) @@ -1175,6 +1359,8 @@ namespace OpenMetaverse optionsOSD.Add(t); } } + */ + loginLLSD["options"] = optionsOSD; // Make the CAPS POST for login @@ -1195,7 +1381,7 @@ namespace OpenMetaverse loginRequest.UserData = CurrentContext; UpdateLoginStatus(LoginStatus.ConnectingToLogin, $"Logging in as {loginParams.FirstName} {loginParams.LastName}..."); - loginRequest.BeginGetResponse(loginLLSD, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + loginRequest.PostRequestAsync(loginLLSD, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); #endregion } @@ -1204,7 +1390,7 @@ namespace OpenMetaverse #region XML-RPC Based Login Code // Create the Hashtable for XmlRpcCs - Hashtable loginXmlRpc = new Hashtable + var loginXmlRpc = new Hashtable { ["first"] = loginParams.FirstName, ["last"] = loginParams.LastName, @@ -1253,7 +1439,7 @@ namespace OpenMetaverse cts.CancelAfter(cc.Timeout); var loginResponse = await HTTP_CLIENT.PostAsXmlRpcAsync(cc.URI, request, cts.Token); cts.Dispose(); - + LoginReplyXmlRpcHandler(loginResponse, loginParams); } catch (Exception e) @@ -1274,7 +1460,7 @@ namespace OpenMetaverse private void UpdateLoginStatus(LoginStatus status, string message) { - InternalStatusCode = status; + LoginStatusCode = status; LoginMessage = message; Logger.DebugLog($"Login status: {status.ToString()}: {message}", Client); @@ -1310,9 +1496,9 @@ namespace OpenMetaverse /// private void LoginReplyXmlRpcHandler(XmlRpcResponse response, LoginParams context) { - LoginResponseData reply = new LoginResponseData(); + var reply = new LoginResponseData(); // Fetch the login response - if (!(response?.Value is Hashtable)) + if (!(response?.Value is Hashtable value)) { UpdateLoginStatus(LoginStatus.Failed, "Invalid or missing login response from the server"); Logger.Log("Invalid or missing login response from the server", Helpers.LogLevel.Warning); @@ -1321,10 +1507,11 @@ namespace OpenMetaverse try { - reply.Parse((Hashtable)response.Value); + reply.Parse(value); if (context.LoginID != CurrentContext.LoginID) { - Logger.Log("Login response does not match login request. Only one login can be attempted at a time", + Logger.Log("Login response does not match login request. " + + "Only one login can be attempted at a time", Helpers.LogLevel.Error); return; } @@ -1348,8 +1535,8 @@ namespace OpenMetaverse ushort simPort = 0; uint regionX = 0; uint regionY = 0; - string reason = reply.Reason; - string message = reply.Message; + var reason = reply.Reason; + var message = reply.Message; if (reply.Login == "true") { @@ -1407,7 +1594,7 @@ namespace OpenMetaverse // reply.initial_outfit } - bool redirect = (reply.Login == "indeterminate"); + var redirect = (reply.Login == "indeterminate"); try { @@ -1429,7 +1616,7 @@ namespace OpenMetaverse loginParams.Options = reply.NextOptions; // Sleep for some amount of time while the servers work - int seconds = reply.NextDuration; + var seconds = reply.NextDuration; Logger.Log($"Sleeping for {seconds} seconds during a login redirect", Helpers.LogLevel.Info); Thread.Sleep(seconds * 1000); @@ -1479,16 +1666,16 @@ namespace OpenMetaverse { if (result != null && result.Type == OSDType.Map) { - OSDMap map = (OSDMap)result; + var map = (OSDMap)result; OSD osd; - LoginResponseData data = new LoginResponseData(); + var data = new LoginResponseData(); data.Parse(map); if (map.TryGetValue("login", out osd)) { - bool loginSuccess = osd.AsBoolean(); - bool redirect = (osd.AsString() == "indeterminate"); + var loginSuccess = osd.AsBoolean(); + var redirect = (osd.AsString() == "indeterminate"); if (redirect) { @@ -1497,12 +1684,12 @@ namespace OpenMetaverse // Make the next login URL jump UpdateLoginStatus(LoginStatus.Redirecting, data.Message); - LoginParams loginParams = CurrentContext; + var loginParams = CurrentContext; loginParams.URI = LoginResponseData.ParseString("next_url", map); //CurrentContext.Params.MethodName = LoginResponseData.ParseString("next_method", map); // Sleep for some amount of time while the servers work - int seconds = (int)LoginResponseData.ParseUInt("next_duration", map); + var seconds = (int)LoginResponseData.ParseUInt("next_duration", map); Logger.Log("Sleeping for " + seconds + " seconds during a login redirect", Helpers.LogLevel.Info); Thread.Sleep(seconds * 1000); @@ -1560,7 +1747,7 @@ namespace OpenMetaverse // Login failed // Make sure a usable error key is set - LoginErrorKey = data.Reason != String.Empty ? data.Reason : "unknown"; + LoginErrorKey = data.Reason != string.Empty ? data.Reason : "unknown"; UpdateLoginStatus(LoginStatus.Failed, data.Message); } @@ -1568,14 +1755,14 @@ namespace OpenMetaverse else { // Got an LLSD map but no login value - UpdateLoginStatus(LoginStatus.Failed, "login parameter missing in the response"); + UpdateLoginStatus(LoginStatus.Failed, "Login parameter missing in the response"); } } else { // No LLSD response LoginErrorKey = "bad response"; - UpdateLoginStatus(LoginStatus.Failed, "Empty or unparseable login response"); + UpdateLoginStatus(LoginStatus.Failed, "Empty or corrupt login response"); } } else @@ -1622,14 +1809,14 @@ namespace OpenMetaverse try { - System.Net.NetworkInformation.NetworkInterface[] nics = + var nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); if (nics.Length > 0) { - foreach (NetworkInterface t in nics) + foreach (var t in nics) { - string adapterMac = t.GetPhysicalAddress().ToString().ToUpper(); + var adapterMac = t.GetPhysicalAddress().ToString().ToUpper(); if (adapterMac.Length == 12 && adapterMac != "000000000000") { mac = adapterMac; @@ -1643,7 +1830,7 @@ namespace OpenMetaverse if (mac.Length < 12) mac = UUID.Random().ToString().Substring(24, 12); - return String.Format("{0}:{1}:{2}:{3}:{4}:{5}", + return string.Format("{0}:{1}:{2}:{3}:{4}:{5}", mac.Substring(0, 2), mac.Substring(2, 2), mac.Substring(4, 2), @@ -1660,8 +1847,8 @@ namespace OpenMetaverse private static string HashString(string str) { MD5 sec = new MD5CryptoServiceProvider(); - ASCIIEncoding enc = new ASCIIEncoding(); - byte[] buf = enc.GetBytes(str); + var enc = new ASCIIEncoding(); + var buf = enc.GetBytes(str); return GetHexString(sec.ComputeHash(buf)); } diff --git a/Assets/Plugins/LibreMetaverse/Messages/LindenMessages.cs b/Assets/Plugins/LibreMetaverse/Messages/LindenMessages.cs index 9b7549c..3137e4b 100644 --- a/Assets/Plugins/LibreMetaverse/Messages/LindenMessages.cs +++ b/Assets/Plugins/LibreMetaverse/Messages/LindenMessages.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -1401,9 +1402,7 @@ namespace OpenMetaverse.Messages.Linden { FolderDataInfo ret = new FolderDataInfo(); - if (!(data is OSDMap)) return ret; - - OSDMap map = (OSDMap)data; + if (!(data is OSDMap map)) return ret; ret.FolderID = map["FolderID"]; ret.ParentID = map["ParentID"]; @@ -1442,9 +1441,7 @@ namespace OpenMetaverse.Messages.Linden { ItemDataInfo ret = new ItemDataInfo(); - if (!(data is OSDMap)) return ret; - - OSDMap map = (OSDMap)data; + if (!(data is OSDMap map)) return ret; ret.ItemID = map["ItemID"]; ret.CallbackID = map["CallbackID"]; diff --git a/Assets/Plugins/LibreMetaverse/NetworkManager.cs b/Assets/Plugins/LibreMetaverse/NetworkManager.cs index 5e74c86..38b5a17 100644 --- a/Assets/Plugins/LibreMetaverse/NetworkManager.cs +++ b/Assets/Plugins/LibreMetaverse/NetworkManager.cs @@ -25,12 +25,10 @@ */ using System; -using System.Collections.Concurrent; using System.Threading; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Net.Http; using System.Threading.Channels; using System.Threading.Tasks; using OpenMetaverse.Packets; @@ -38,7 +36,7 @@ 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 @@ -316,7 +314,7 @@ namespace OpenMetaverse /// Shows whether the network layer is logged in to the /// grid or not - public bool Connected => connected; + public bool Connected { get; private set; } /// Number of packets in the incoming queue public int InboxCount => _packetInboxCount; @@ -346,7 +344,6 @@ namespace OpenMetaverse private GridClient Client; private Timer DisconnectTimer; - private bool connected; private long lastpacketwarning = 0; @@ -587,7 +584,7 @@ namespace OpenMetaverse { // Mark that we are connecting/connected to the grid // - connected = true; + Connected = true; // raise the SimConnecting event and allow any event // subscribers to cancel the connection @@ -746,7 +743,7 @@ namespace OpenMetaverse } // This will catch a Logout when the client is not logged in - if (CurrentSim == null || !connected) + if (CurrentSim == null || !Connected) { Logger.Log("Ignoring RequestLogout(), client is already logged out", Helpers.LogLevel.Warning, Client); return; @@ -865,7 +862,7 @@ namespace OpenMetaverse Interlocked.Exchange(ref _packetInboxCount, 0); Interlocked.Exchange(ref _packetOutboxCount, 0); - connected = false; + Connected = false; // Fire the disconnected callback if (m_Disconnected != null) @@ -923,7 +920,7 @@ namespace OpenMetaverse // 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 (await reader.WaitToReadAsync() && Connected) { while (reader.TryRead(out var outgoingPacket)) { @@ -956,7 +953,7 @@ namespace OpenMetaverse { var reader = _packetInbox.Reader; - while (await reader.WaitToReadAsync() && connected) + while (await reader.WaitToReadAsync() && Connected) { while (reader.TryRead(out var incomingPacket)) { @@ -1006,14 +1003,14 @@ namespace OpenMetaverse private void DisconnectTimer_Elapsed(object obj) { - if (!connected || CurrentSim == null) + if (!Connected || CurrentSim == null) { if (DisconnectTimer != null) { DisconnectTimer.Dispose(); DisconnectTimer = null; } - connected = false; + Connected = false; } else if (CurrentSim.DisconnectCandidate) { @@ -1027,7 +1024,7 @@ namespace OpenMetaverse DisconnectTimer = null; } - connected = false; + Connected = false; // Shutdown the network layer Shutdown(DisconnectType.NetworkTimeout); diff --git a/Assets/Plugins/LibreMetaverse/ObjectManager.cs b/Assets/Plugins/LibreMetaverse/ObjectManager.cs index f6829f4..d1285ee 100644 --- a/Assets/Plugins/LibreMetaverse/ObjectManager.cs +++ b/Assets/Plugins/LibreMetaverse/ObjectManager.cs @@ -1696,7 +1696,7 @@ namespace OpenMetaverse } }; - request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -1725,7 +1725,7 @@ namespace OpenMetaverse Logger.Log("ObjectMediaUpdate: " + error.Message, Helpers.LogLevel.Error, Client); } }; - request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -1781,7 +1781,7 @@ namespace OpenMetaverse } }; - request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -3359,22 +3359,20 @@ namespace OpenMetaverse /// public class PrimEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly bool m_IsNew; - private readonly bool m_IsAttachment; - private readonly Primitive m_Prim; - private readonly ushort m_TimeDilation; - /// Get the simulator the originated from - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// Get the details - public Primitive Prim { get { return m_Prim; } } + public Primitive Prim { get; } + /// true if the did not exist in the dictionary before this update (always true if object tracking has been disabled) - public bool IsNew { get { return m_IsNew; } } + public bool IsNew { get; } + /// true if the is attached to an - public bool IsAttachment { get { return m_IsAttachment; } } + public bool IsAttachment { get; } + /// Get the simulator Time Dilation - public ushort TimeDilation { get { return m_TimeDilation; } } + public ushort TimeDilation { get; } /// /// Construct a new instance of the PrimEventArgs class @@ -3386,11 +3384,11 @@ namespace OpenMetaverse /// true if the primitive represents an attachment to an agent public PrimEventArgs(Simulator simulator, Primitive prim, ushort timeDilation, bool isNew, bool isAttachment) { - this.m_Simulator = simulator; - this.m_IsNew = isNew; - this.m_IsAttachment = isAttachment; - this.m_Prim = prim; - this.m_TimeDilation = timeDilation; + this.Simulator = simulator; + this.IsNew = isNew; + this.IsAttachment = isAttachment; + this.Prim = prim; + this.TimeDilation = timeDilation; } } @@ -3440,19 +3438,17 @@ namespace OpenMetaverse /// public class AvatarUpdateEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly Avatar m_Avatar; - private readonly ushort m_TimeDilation; - private readonly bool m_IsNew; - /// Get the simulator the object originated from - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// Get the data - public Avatar Avatar { get { return m_Avatar; } } + public Avatar Avatar { get; } + /// Get the simulator time dilation - public ushort TimeDilation { get { return m_TimeDilation; } } + public ushort TimeDilation { get; } + /// true if the did not exist in the dictionary before this update (always true if avatar tracking has been disabled) - public bool IsNew { get { return m_IsNew; } } + public bool IsNew { get; } /// /// Construct a new instance of the AvatarUpdateEventArgs class @@ -3463,24 +3459,22 @@ namespace OpenMetaverse /// The avatar was not in the dictionary before this update public AvatarUpdateEventArgs(Simulator simulator, Avatar avatar, ushort timeDilation, bool isNew) { - this.m_Simulator = simulator; - this.m_Avatar = avatar; - this.m_TimeDilation = timeDilation; - this.m_IsNew = isNew; + this.Simulator = simulator; + this.Avatar = avatar; + this.TimeDilation = timeDilation; + this.IsNew = isNew; } } public class ParticleUpdateEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly Primitive.ParticleSystem m_ParticleSystem; - private readonly Primitive m_Source; - /// Get the simulator the object originated from - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// Get the data - public Primitive.ParticleSystem ParticleSystem { get { return m_ParticleSystem; } } + public Primitive.ParticleSystem ParticleSystem { get; } + /// Get source - public Primitive Source { get { return m_Source; } } + public Primitive Source { get; } /// /// Construct a new instance of the ParticleUpdateEventArgs class @@ -3489,9 +3483,9 @@ namespace OpenMetaverse /// The ParticleSystem data /// The Primitive source public ParticleUpdateEventArgs(Simulator simulator, Primitive.ParticleSystem particlesystem, Primitive source) { - this.m_Simulator = simulator; - this.m_ParticleSystem = particlesystem; - this.m_Source = source; + this.Simulator = simulator; + this.ParticleSystem = particlesystem; + this.Source = source; } } @@ -3548,11 +3542,8 @@ namespace OpenMetaverse /// public class ObjectPropertiesUpdatedEventArgs : ObjectPropertiesEventArgs { - - private readonly Primitive m_Prim; - /// Get the primitive details - public Primitive Prim { get { return m_Prim; } } + public Primitive Prim { get; } /// /// Construct a new instance of the ObjectPropertiesUpdatedEvenrArgs class @@ -3562,7 +3553,7 @@ namespace OpenMetaverse /// The primitive Properties public ObjectPropertiesUpdatedEventArgs(Simulator simulator, Primitive prim, Primitive.ObjectProperties props) : base(simulator, props) { - this.m_Prim = prim; + this.Prim = prim; } } @@ -3575,22 +3566,20 @@ namespace OpenMetaverse /// public class ObjectPropertiesFamilyEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly Primitive.ObjectProperties m_Properties; - private readonly ReportType m_Type; - /// Get the simulator the object is located - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// - public Primitive.ObjectProperties Properties { get { return m_Properties; } } + public Primitive.ObjectProperties Properties { get; } + /// - public ReportType Type { get { return m_Type; } } + public ReportType Type { get; } public ObjectPropertiesFamilyEventArgs(Simulator simulator, Primitive.ObjectProperties props, ReportType type) { - this.m_Simulator = simulator; - this.m_Properties = props; - this.m_Type = type; + this.Simulator = simulator; + this.Properties = props; + this.Type = type; } } @@ -3599,26 +3588,24 @@ namespace OpenMetaverse /// public class TerseObjectUpdateEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly Primitive m_Prim; - private readonly ObjectMovementUpdate m_Update; - private readonly ushort m_TimeDilation; - /// Get the simulator the object is located - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// Get the primitive details - public Primitive Prim { get { return m_Prim; } } + public Primitive Prim { get; } + /// - public ObjectMovementUpdate Update { get { return m_Update; } } + public ObjectMovementUpdate Update { get; } + /// - public ushort TimeDilation { get { return m_TimeDilation; } } + public ushort TimeDilation { get; } public TerseObjectUpdateEventArgs(Simulator simulator, Primitive prim, ObjectMovementUpdate update, ushort timeDilation) { - this.m_Simulator = simulator; - this.m_Prim = prim; - this.m_Update = update; - this.m_TimeDilation = timeDilation; + this.Simulator = simulator; + this.Prim = prim; + this.Update = update; + this.TimeDilation = timeDilation; } } @@ -3627,35 +3614,33 @@ namespace OpenMetaverse /// public class ObjectDataBlockUpdateEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly Primitive m_Prim; - private readonly Primitive.ConstructionData m_ConstructionData; - private readonly ObjectUpdatePacket.ObjectDataBlock m_Block; - private readonly ObjectMovementUpdate m_Update; - private readonly NameValue[] m_NameValues; - /// Get the simulator the object is located - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// Get the primitive details - public Primitive Prim { get { return m_Prim; } } + public Primitive Prim { get; } + /// - public Primitive.ConstructionData ConstructionData { get { return m_ConstructionData; } } + public Primitive.ConstructionData ConstructionData { get; } + /// - public ObjectUpdatePacket.ObjectDataBlock Block { get { return m_Block; } } + public ObjectUpdatePacket.ObjectDataBlock Block { get; } + /// - public ObjectMovementUpdate Update { get { return m_Update; } } + public ObjectMovementUpdate Update { get; } + /// - public NameValue[] NameValues { get { return m_NameValues; } } + public NameValue[] NameValues { get; } public ObjectDataBlockUpdateEventArgs(Simulator simulator, Primitive prim, Primitive.ConstructionData constructionData, ObjectUpdatePacket.ObjectDataBlock block, ObjectMovementUpdate objectupdate, NameValue[] nameValues) { - this.m_Simulator = simulator; - this.m_Prim = prim; - this.m_ConstructionData = constructionData; - this.m_Block = block; - this.m_Update = objectupdate; - this.m_NameValues = nameValues; + this.Simulator = simulator; + this.Prim = prim; + this.ConstructionData = constructionData; + this.Block = block; + this.Update = objectupdate; + this.NameValues = nameValues; } } @@ -3663,18 +3648,16 @@ namespace OpenMetaverse /// event public class KillObjectEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly uint m_ObjectLocalID; - /// Get the simulator the object is located - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// The LocalID of the object - public uint ObjectLocalID { get { return m_ObjectLocalID; } } + public uint ObjectLocalID { get; } public KillObjectEventArgs(Simulator simulator, uint objectID) { - this.m_Simulator = simulator; - this.m_ObjectLocalID = objectID; + this.Simulator = simulator; + this.ObjectLocalID = objectID; } } @@ -3682,18 +3665,16 @@ namespace OpenMetaverse /// event public class KillObjectsEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly uint[] m_ObjectLocalIDs; - /// Get the simulator the object is located - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// The LocalID of the object - public uint[] ObjectLocalIDs { get { return m_ObjectLocalIDs; } } + public uint[] ObjectLocalIDs { get; } public KillObjectsEventArgs(Simulator simulator, uint[] objectIDs) { - this.m_Simulator = simulator; - this.m_ObjectLocalIDs = objectIDs; + this.Simulator = simulator; + this.ObjectLocalIDs = objectIDs; } } @@ -3702,26 +3683,24 @@ namespace OpenMetaverse /// public class AvatarSitChangedEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly Avatar m_Avatar; - private readonly uint m_SittingOn; - private readonly uint m_OldSeat; - /// Get the simulator the object is located - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// - public Avatar Avatar { get { return m_Avatar; } } + public Avatar Avatar { get; } + /// - public uint SittingOn { get { return m_SittingOn; } } + public uint SittingOn { get; } + /// - public uint OldSeat { get { return m_OldSeat; } } + public uint OldSeat { get; } public AvatarSitChangedEventArgs(Simulator simulator, Avatar avatar, uint sittingOn, uint oldSeat) { - this.m_Simulator = simulator; - this.m_Avatar = avatar; - this.m_SittingOn = sittingOn; - this.m_OldSeat = oldSeat; + this.Simulator = simulator; + this.Avatar = avatar; + this.SittingOn = sittingOn; + this.OldSeat = oldSeat; } } @@ -3730,26 +3709,24 @@ namespace OpenMetaverse /// public class PayPriceReplyEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly UUID m_ObjectID; - private readonly int m_DefaultPrice; - private readonly int[] m_ButtonPrices; - /// Get the simulator the object is located - public Simulator Simulator { get { return m_Simulator; } } + public Simulator Simulator { get; } + /// - public UUID ObjectID { get { return m_ObjectID; } } + public UUID ObjectID { get; } + /// - public int DefaultPrice { get { return m_DefaultPrice; } } + public int DefaultPrice { get; } + /// - public int[] ButtonPrices { get { return m_ButtonPrices; } } + public int[] ButtonPrices { get; } public PayPriceReplyEventArgs(Simulator simulator, UUID objectID, int defaultPrice, int[] buttonPrices) { - this.m_Simulator = simulator; - this.m_ObjectID = objectID; - this.m_DefaultPrice = defaultPrice; - this.m_ButtonPrices = buttonPrices; + this.Simulator = simulator; + this.ObjectID = objectID; + this.DefaultPrice = defaultPrice; + this.ButtonPrices = buttonPrices; } } diff --git a/Assets/Plugins/LibreMetaverse/PacketDecoder.cs b/Assets/Plugins/LibreMetaverse/PacketDecoder.cs index 4e93f7b..f384cd9 100644 --- a/Assets/Plugins/LibreMetaverse/PacketDecoder.cs +++ b/Assets/Plugins/LibreMetaverse/PacketDecoder.cs @@ -26,7 +26,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Reflection; using System.Text; diff --git a/Assets/Plugins/LibreMetaverse/ParcelManager.cs b/Assets/Plugins/LibreMetaverse/ParcelManager.cs index 02fa521..2bfc1e7 100644 --- a/Assets/Plugins/LibreMetaverse/ParcelManager.cs +++ b/Assets/Plugins/LibreMetaverse/ParcelManager.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -570,6 +571,12 @@ namespace OpenMetaverse public bool ObscureMusic; /// A struct containing media details public ParcelMedia Media; + /// Parcel privacy see avatars outside/inside parcel + public bool SeeAVs; + /// Parcel privacy play sounds attached to avatars outside/inside parcel + public bool AnyAVSounds; + /// Parcel privacy play sounds for group members + public bool GroupAVSounds; /// /// Displays a parcel object in string format @@ -635,12 +642,15 @@ namespace OpenMetaverse SalePrice = (uint) SalePrice, SnapshotID = SnapshotID, UserLocation = UserLocation, - UserLookAt = UserLookAt + UserLookAt = UserLookAt, + SeeAVs = SeeAVs, + AnyAVSounds = AnyAVSounds, + GroupAVSounds = GroupAVSounds }; OSDMap body = req.Serialize(); - request.BeginGetResponse(body, OSDFormat.Xml, simulator.Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(body, OSDFormat.Xml, simulator.Client.Settings.CAPS_TIMEOUT); } else { @@ -1661,9 +1671,9 @@ namespace OpenMetaverse try { - OSD result = request.GetResponse(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + OSDMap result = request.PostRequest(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT) as OSDMap; RemoteParcelRequestReply response = new RemoteParcelRequestReply(); - response.Deserialize((OSDMap)result); + response.Deserialize(result); return response.ParcelID; } catch (Exception) @@ -1700,7 +1710,7 @@ namespace OpenMetaverse response.Deserialize((OSDMap)result); CapsClient summaryRequest = new CapsClient(response.ScriptResourceSummary, "ScriptResourceSummary"); - OSD summaryResponse = summaryRequest.GetResponse(Client.Settings.CAPS_TIMEOUT); + OSD summaryResponse = summaryRequest.GetRequest(Client.Settings.CAPS_TIMEOUT); LandResourcesInfo res = new LandResourcesInfo(); res.Deserialize((OSDMap)summaryResponse); @@ -1708,7 +1718,7 @@ namespace OpenMetaverse if (response.ScriptResourceDetails != null && getDetails) { CapsClient detailRequest = new CapsClient(response.ScriptResourceDetails, "ScriptResourceDetails"); - OSD detailResponse = detailRequest.GetResponse(Client.Settings.CAPS_TIMEOUT); + OSD detailResponse = detailRequest.GetRequest(Client.Settings.CAPS_TIMEOUT); res.Deserialize((OSDMap)detailResponse); } callback(true, res); @@ -1721,7 +1731,7 @@ namespace OpenMetaverse }; LandResourcesRequest param = new LandResourcesRequest {ParcelID = parcelID}; - request.BeginGetResponse(param.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + request.PostRequestAsync(param.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } catch (Exception ex) @@ -1842,7 +1852,11 @@ namespace OpenMetaverse RegionDenyAgeUnverified = msg.RegionDenyAgeUnverified, RegionDenyAnonymous = msg.RegionDenyAnonymous, RegionPushOverride = msg.RegionPushOverride, - RentPrice = msg.RentPrice + RentPrice = msg.RentPrice, + SeeAVs = msg.SeeAVs, + AnyAVSounds = msg.AnyAVSounds, + GroupAVSounds = msg.GroupAVSounds + }; ParcelResult result = msg.RequestResult; @@ -2100,18 +2114,14 @@ namespace OpenMetaverse /// Contains a parcels dwell data returned from the simulator in response to an public class ParcelDwellReplyEventArgs : EventArgs { - private readonly UUID m_ParcelID; - private readonly int m_LocalID; - private readonly float m_Dwell; - /// Get the global ID of the parcel - public UUID ParcelID => m_ParcelID; + public UUID ParcelID { get; } /// Get the simulator specific ID of the parcel - public int LocalID => m_LocalID; + public int LocalID { get; } /// Get the calculated dwell - public float Dwell => m_Dwell; + public float Dwell { get; } /// /// Construct a new instance of the ParcelDwellReplyEventArgs class @@ -2121,20 +2131,18 @@ namespace OpenMetaverse /// The calculated dwell for the parcel public ParcelDwellReplyEventArgs(UUID parcelID, int localID, float dwell) { - m_ParcelID = parcelID; - m_LocalID = localID; - m_Dwell = dwell; + ParcelID = parcelID; + LocalID = localID; + Dwell = dwell; } } /// Contains basic parcel information data returned from the /// simulator in response to an request public class ParcelInfoReplyEventArgs : EventArgs - { - private readonly ParcelInfo m_Parcel; - + { /// Get the object containing basic parcel info - public ParcelInfo Parcel => m_Parcel; + public ParcelInfo Parcel { get; } /// /// Construct a new instance of the ParcelInfoReplyEventArgs class @@ -2142,40 +2150,33 @@ namespace OpenMetaverse /// The object containing basic parcel info public ParcelInfoReplyEventArgs(ParcelInfo parcel) { - m_Parcel = parcel; + Parcel = parcel; } } /// Contains basic parcel information data returned from the simulator in response to an request public class ParcelPropertiesEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private Parcel m_Parcel; - private readonly ParcelResult m_Result; - private readonly int m_SelectedPrims; - private readonly int m_SequenceID; - private readonly bool m_SnapSelection; - /// Get the simulator the parcel is located in - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// Get the object containing the details /// If Result is NoData, this object will not contain valid data - public Parcel Parcel => m_Parcel; + public Parcel Parcel { get; } /// Get the result of the request - public ParcelResult Result => m_Result; + public ParcelResult Result { get; } /// Get the number of primitieves your agent is /// currently selecting and or sitting on in this parcel - public int SelectedPrims => m_SelectedPrims; + public int SelectedPrims { get; } /// Get the user assigned ID used to correlate a request with /// these results - public int SequenceID => m_SequenceID; + public int SequenceID { get; } /// TODO: - public bool SnapSelection => m_SnapSelection; + public bool SnapSelection { get; } /// /// Construct a new instance of the ParcelPropertiesEventArgs class @@ -2191,39 +2192,33 @@ namespace OpenMetaverse public ParcelPropertiesEventArgs(Simulator simulator, Parcel parcel, ParcelResult result, int selectedPrims, int sequenceID, bool snapSelection) { - m_Simulator = simulator; - m_Parcel = parcel; - m_Result = result; - m_SelectedPrims = selectedPrims; - m_SequenceID = sequenceID; - m_SnapSelection = snapSelection; + Simulator = simulator; + Parcel = parcel; + Result = result; + SelectedPrims = selectedPrims; + SequenceID = sequenceID; + SnapSelection = snapSelection; } } /// Contains blacklist and whitelist data returned from the simulator in response to an request public class ParcelAccessListReplyEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly int m_SequenceID; - private readonly int m_LocalID; - private readonly uint m_Flags; - private readonly List m_AccessList; - /// Get the simulator the parcel is located in - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// Get the user assigned ID used to correlate a request with /// these results - public int SequenceID => m_SequenceID; + public int SequenceID { get; } /// Get the simulator specific ID of the parcel - public int LocalID => m_LocalID; + public int LocalID { get; } /// TODO - public uint Flags => m_Flags; + public uint Flags { get; } /// Get the list containing the white/blacklisted agents for the parcel - public List AccessList => m_AccessList; + public List AccessList { get; } /// /// Construct a new instance of the ParcelAccessListReplyEventArgs class @@ -2236,11 +2231,11 @@ namespace OpenMetaverse /// The list containing the white/blacklisted agents for the parcel public ParcelAccessListReplyEventArgs(Simulator simulator, int sequenceID, int localID, uint flags, List accessEntries) { - m_Simulator = simulator; - m_SequenceID = sequenceID; - m_LocalID = localID; - m_Flags = flags; - m_AccessList = accessEntries; + Simulator = simulator; + SequenceID = sequenceID; + LocalID = localID; + Flags = flags; + AccessList = accessEntries; } } @@ -2248,14 +2243,11 @@ namespace OpenMetaverse /// simulator in response to an request public class ParcelObjectOwnersReplyEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly List m_Owners; - /// Get the simulator the parcel is located in - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// Get the list containing prim ownership counts - public List PrimOwners => m_Owners; + public List PrimOwners { get; } /// /// Construct a new instance of the ParcelObjectOwnersReplyEventArgs class @@ -2264,27 +2256,23 @@ namespace OpenMetaverse /// The list containing prim ownership counts public ParcelObjectOwnersReplyEventArgs(Simulator simulator, List primOwners) { - m_Simulator = simulator; - m_Owners = primOwners; + Simulator = simulator; + PrimOwners = primOwners; } } /// Contains the data returned when all parcel data has been retrieved from a simulator public class SimParcelsDownloadedEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly InternalDictionary m_Parcels; - private readonly int[,] m_ParcelMap; - /// Get the simulator the parcel data was retrieved from - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// A dictionary containing the parcel data where the key correlates to the ParcelMap entry - public InternalDictionary Parcels => m_Parcels; + public InternalDictionary Parcels { get; } /// Get the multidimensional array containing a x,y grid mapped /// to each 64x64 parcel's LocalID. - public int[,] ParcelMap => m_ParcelMap; + public int[,] ParcelMap { get; } /// /// Construct a new instance of the SimParcelsDownloadedEventArgs class @@ -2295,28 +2283,24 @@ namespace OpenMetaverse /// to each 64x64 parcel's LocalID. public SimParcelsDownloadedEventArgs(Simulator simulator, InternalDictionary simParcels, int[,] parcelMap) { - m_Simulator = simulator; - m_Parcels = simParcels; - m_ParcelMap = parcelMap; + Simulator = simulator; + Parcels = simParcels; + ParcelMap = parcelMap; } } /// Contains the data returned when a request public class ForceSelectObjectsReplyEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly List m_ObjectIDs; - private readonly bool m_ResetList; - /// Get the simulator the parcel data was retrieved from - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// Get the list of primitive IDs - public List ObjectIDs => m_ObjectIDs; + public List ObjectIDs { get; } /// true if the list is clean and contains the information /// only for a given request - public bool ResetList => m_ResetList; + public bool ResetList { get; } /// /// Construct a new instance of the ForceSelectObjectsReplyEventArgs class @@ -2327,23 +2311,20 @@ namespace OpenMetaverse /// only for a given request public ForceSelectObjectsReplyEventArgs(Simulator simulator, List objectIDs, bool resetList) { - this.m_Simulator = simulator; - this.m_ObjectIDs = objectIDs; - this.m_ResetList = resetList; + this.Simulator = simulator; + this.ObjectIDs = objectIDs; + this.ResetList = resetList; } } /// Contains data when the media data for a parcel the avatar is on changes public class ParcelMediaUpdateReplyEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly ParcelMedia m_ParcelMedia; - /// Get the simulator the parcel media data was updated in - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// Get the updated media information - public ParcelMedia Media => m_ParcelMedia; + public ParcelMedia Media { get; } /// /// Construct a new instance of the ParcelMediaUpdateReplyEventArgs class @@ -2352,34 +2333,28 @@ namespace OpenMetaverse /// The updated media information public ParcelMediaUpdateReplyEventArgs(Simulator simulator, ParcelMedia media) { - this.m_Simulator = simulator; - this.m_ParcelMedia = media; + this.Simulator = simulator; + this.Media = media; } } /// Contains the media command for a parcel the agent is currently on public class ParcelMediaCommandEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly uint m_Sequence; - private readonly ParcelFlags m_ParcelFlags; - private readonly ParcelMediaCommand m_MediaCommand; - private readonly float m_Time; - /// Get the simulator the parcel media command was issued in - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// - public uint Sequence => m_Sequence; + public uint Sequence { get; } /// - public ParcelFlags ParcelFlags => m_ParcelFlags; + public ParcelFlags ParcelFlags { get; } /// Get the media command that was sent - public ParcelMediaCommand MediaCommand => m_MediaCommand; + public ParcelMediaCommand MediaCommand { get; } /// - public float Time => m_Time; + public float Time { get; } /// /// Construct a new instance of the ParcelMediaCommandEventArgs class @@ -2391,11 +2366,11 @@ namespace OpenMetaverse /// public ParcelMediaCommandEventArgs(Simulator simulator, uint sequence, ParcelFlags flags, ParcelMediaCommand command, float time) { - m_Simulator = simulator; - m_Sequence = sequence; - m_ParcelFlags = flags; - m_MediaCommand = command; - m_Time = time; + Simulator = simulator; + Sequence = sequence; + ParcelFlags = flags; + MediaCommand = command; + Time = time; } } #endregion diff --git a/Assets/Plugins/LibreMetaverse/Permissions.cs b/Assets/Plugins/LibreMetaverse/Permissions.cs index bd21da1..7e506fc 100644 --- a/Assets/Plugins/LibreMetaverse/Permissions.cs +++ b/Assets/Plugins/LibreMetaverse/Permissions.cs @@ -29,9 +29,6 @@ using OpenMetaverse.StructuredData; namespace OpenMetaverse { - /// - /// - /// [Flags] public enum PermissionMask : uint { @@ -47,9 +44,6 @@ namespace OpenMetaverse All = (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19) } - /// - /// - /// [Flags] public enum PermissionWho : byte { @@ -67,10 +61,7 @@ namespace OpenMetaverse All = 0x1F } - /// - /// - /// - [Serializable()] + [Serializable] public struct Permissions { public PermissionMask BaseMask; diff --git a/Assets/Plugins/LibreMetaverse/PrimMesher/ObjMesh.cs b/Assets/Plugins/LibreMetaverse/PrimMesher/ObjMesh.cs index 01edcf5..f38d220 100644 --- a/Assets/Plugins/LibreMetaverse/PrimMesher/ObjMesh.cs +++ b/Assets/Plugins/LibreMetaverse/PrimMesher/ObjMesh.cs @@ -66,7 +66,7 @@ namespace LibreMetaverse.PrimMesher while (!s.EndOfStream) { - var line = s.ReadLine().Trim(); + var line = s.ReadLine()?.Trim(); var tokens = Regex.Split(line, @"\s+"); // Skip blank lines and comments diff --git a/Assets/Plugins/LibreMetaverse/PrimMesher/PrimMesher.cs b/Assets/Plugins/LibreMetaverse/PrimMesher/PrimMesher.cs index af11e8c..4ab6e10 100644 --- a/Assets/Plugins/LibreMetaverse/PrimMesher/PrimMesher.cs +++ b/Assets/Plugins/LibreMetaverse/PrimMesher/PrimMesher.cs @@ -24,8 +24,8 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#define VERTEX_INDEXER +#define VERTEX_INDEXER using System; using System.Collections.Generic; using System.IO; diff --git a/Assets/Plugins/LibreMetaverse/PrimMesher/SculptMap.cs b/Assets/Plugins/LibreMetaverse/PrimMesher/SculptMap.cs index b427c01..029afea 100644 --- a/Assets/Plugins/LibreMetaverse/PrimMesher/SculptMap.cs +++ b/Assets/Plugins/LibreMetaverse/PrimMesher/SculptMap.cs @@ -73,9 +73,8 @@ namespace LibreMetaverse.PrimMesher try { if (needsScaling) - bm.Resize(width,height); - //bm = ScaleImage(bm, width, height, - // InterpolationMode.NearestNeighbor); + //bm.Resize(width,height); + ScaleImage(bm, width, height); } catch (Exception e) @@ -167,6 +166,12 @@ namespace LibreMetaverse.PrimMesher return rows; } + private void ScaleImage(Texture2D srcImage, int destWidth, int destHeight) + { + srcImage.Resize(destWidth, destHeight); + srcImage.Apply(); + } + //private Bitmap ScaleImage(Bitmap srcImage, int destWidth, int destHeight, // InterpolationMode interpMode) //{ diff --git a/Assets/Plugins/LibreMetaverse/PrimMesher/SculptMesh.cs b/Assets/Plugins/LibreMetaverse/PrimMesher/SculptMesh.cs index 04c1386..0783bfb 100644 --- a/Assets/Plugins/LibreMetaverse/PrimMesher/SculptMesh.cs +++ b/Assets/Plugins/LibreMetaverse/PrimMesher/SculptMesh.cs @@ -58,18 +58,9 @@ namespace LibreMetaverse.PrimMesher // //public SculptMesh(string fileName, int sculptType, int lod, int viewerMode, int mirror, int invert) //{ - // //var bitmap = (Bitmap) Image.FromFile(fileName); - // var myreader = new BMPLoader(); - // BMPImage myimg = myreader.LoadBMP(fileName); - // Texture2D tex = myimg.ToTexture2D(); - // Texture2D fakebmp = new Texture2D(tex); - - // _SculptMesh(fakebmp, (SculptType) sculptType, lod, viewerMode != 0, mirror != 0, invert != 0); - // //bitmap.Dispose(); - - // fakebmp.delete(); - - + // var bitmap = (Bitmap) Image.FromFile(fileName); + // _SculptMesh(bitmap, (SculptType) sculptType, lod, viewerMode != 0, mirror != 0, invert != 0); + // bitmap.Dispose(); //} /// diff --git a/Assets/Plugins/LibreMetaverse/Primitives/ParticleSystem.cs b/Assets/Plugins/LibreMetaverse/Primitives/ParticleSystem.cs index 2c01235..ab1a0c8 100644 --- a/Assets/Plugins/LibreMetaverse/Primitives/ParticleSystem.cs +++ b/Assets/Plugins/LibreMetaverse/Primitives/ParticleSystem.cs @@ -25,8 +25,6 @@ */ using System; -using System.Collections.Generic; -using System.ComponentModel; using OpenMetaverse.StructuredData; namespace OpenMetaverse @@ -46,6 +44,7 @@ namespace OpenMetaverse /// /// Particle source pattern /// + [Flags] public enum SourcePattern : byte { /// None diff --git a/Assets/Plugins/LibreMetaverse/Primitives/Primitive.cs b/Assets/Plugins/LibreMetaverse/Primitives/Primitive.cs index 49fc768..20c25c0 100644 --- a/Assets/Plugins/LibreMetaverse/Primitives/Primitive.cs +++ b/Assets/Plugins/LibreMetaverse/Primitives/Primitive.cs @@ -25,7 +25,6 @@ */ using System; -using System.Collections.Generic; using OpenMetaverse.StructuredData; namespace OpenMetaverse @@ -744,9 +743,8 @@ namespace OpenMetaverse { PhysicsProperties ret = new PhysicsProperties(); - if (osd is OSDMap) + if (osd is OSDMap map) { - OSDMap map = (OSDMap)osd; ret.LocalID = map["LocalID"]; ret.Density = map["Density"]; ret.Friction = map["Friction"]; @@ -1317,7 +1315,7 @@ namespace OpenMetaverse public override bool Equals(object obj) { - return (obj is Primitive) ? this == (Primitive)obj : false; + return (obj is Primitive primitive) && this == primitive; } public bool Equals(Primitive other) diff --git a/Assets/Plugins/LibreMetaverse/Rendering/Rendering.cs b/Assets/Plugins/LibreMetaverse/Rendering/Rendering.cs index a76bb4a..70003d0 100644 --- a/Assets/Plugins/LibreMetaverse/Rendering/Rendering.cs +++ b/Assets/Plugins/LibreMetaverse/Rendering/Rendering.cs @@ -116,7 +116,7 @@ namespace OpenMetaverse.Rendering public override bool Equals(object obj) { - return (obj is Vertex) && this == (Vertex)obj; + return (obj is Vertex vertex) && this == vertex; } public bool Equals(Vertex other) @@ -301,11 +301,9 @@ namespace OpenMetaverse.Rendering OSD subMeshOsd = decodedMeshOsdArray[faceNr]; // Decode each individual face - if (subMeshOsd is OSDMap) + if (subMeshOsd is OSDMap subMeshMap) { - OSDMap subMeshMap = (OSDMap)subMeshOsd; - - // As per http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format, some Mesh Level + // As per http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format, some Mesh Level // of Detail Blocks (maps) contain just a NoGeometry key to signal there is no // geometry for this submesh. if (subMeshMap.ContainsKey("NoGeometry") && ((OSDBoolean)subMeshMap["NoGeometry"])) diff --git a/Assets/Plugins/LibreMetaverse/Settings.cs b/Assets/Plugins/LibreMetaverse/Settings.cs index 0f662c8..336aeda 100644 --- a/Assets/Plugins/LibreMetaverse/Settings.cs +++ b/Assets/Plugins/LibreMetaverse/Settings.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -37,6 +38,8 @@ namespace OpenMetaverse /// happen at login or dynamically public class Settings { + public static string USER_AGENT = "LibreMetaverse"; + #region Login/Networking Settings /// Main grid login server @@ -55,7 +58,7 @@ namespace OpenMetaverse public static System.Net.IPAddress BIND_ADDR = System.Net.IPAddress.Any; /// Use XML-RPC Login or LLSD Login, default is XML-RPC Login - public bool USE_LLSD_LOGIN = true; + public bool USE_LLSD_LOGIN = true; //why is the false by default? /// /// Maximum number of HTTP connections to open to a particular endpoint. @@ -80,10 +83,6 @@ namespace OpenMetaverse /// GridClient initializes an Inventory store for the library. /// public const bool ENABLE_LIBRARY_STORE = true; - /// - /// Use Caps for fetching inventory where available - /// - public bool HTTP_INVENTORY = true; #endregion @@ -227,10 +226,6 @@ namespace OpenMetaverse /// re-establish a connection. Set this to true to log those 502 errors public bool LOG_ALL_CAPS_ERRORS = false; - /// If true, any reference received for a folder or item - /// the library is not aware of will automatically be fetched - public bool FETCH_MISSING_INVENTORY = true; - /// If true, and SEND_AGENT_UPDATES is true, /// AgentUpdate packets will continuously be sent out to give the bot /// smoother movement and autopiloting @@ -302,7 +297,7 @@ namespace OpenMetaverse /// Cost of uploading an asset /// Read-only since this value is dynamically fetched at login - public int UPLOAD_COST => priceUpload; + public int UPLOAD_COST { get; private set; } = 0; /// Maximum number of times to resend a failed packet public int MAX_RESEND_COUNT = 3; @@ -310,9 +305,6 @@ namespace OpenMetaverse /// Throttle outgoing packet rate public bool THROTTLE_OUTGOING_PACKETS = true; - /// UUID of a texture used by some viewers to indentify type of client used - public UUID CLIENT_IDENTIFICATION_TAG = UUID.Zero; - #endregion #region Texture Pipeline @@ -362,7 +354,6 @@ namespace OpenMetaverse #region Private Fields private GridClient Client; - private int priceUpload = 0; public static bool SORT_INVENTORY = false; /// Constructor @@ -383,7 +374,7 @@ namespace OpenMetaverse { EconomyDataPacket econ = (EconomyDataPacket)e.Packet; - priceUpload = econ.Info.PriceUpload; //TODO: is this thread safe? no lock + UPLOAD_COST = econ.Info.PriceUpload; //TODO: is this thread safe? no lock } #endregion diff --git a/Assets/Plugins/LibreMetaverse/SoundManager.cs b/Assets/Plugins/LibreMetaverse/SoundManager.cs index 9b3d105..e152070 100644 --- a/Assets/Plugins/LibreMetaverse/SoundManager.cs +++ b/Assets/Plugins/LibreMetaverse/SoundManager.cs @@ -343,18 +343,14 @@ namespace OpenMetaverse /// changes its volume level public class AttachedSoundGainChangeEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly UUID m_ObjectID; - private readonly float m_Gain; - /// Simulator where the event originated - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// Get the ID of the Object - public UUID ObjectID => m_ObjectID; + public UUID ObjectID { get; } /// Get the volume level - public float Gain => m_Gain; + public float Gain { get; } /// /// Construct a new instance of the AttachedSoundGainChangedEventArgs class @@ -364,9 +360,9 @@ namespace OpenMetaverse /// The new volume level public AttachedSoundGainChangeEventArgs(Simulator sim, UUID objectID, float gain) { - m_Simulator = sim; - m_ObjectID = objectID; - m_Gain = gain; + Simulator = sim; + ObjectID = objectID; + Gain = gain; } } @@ -398,38 +394,29 @@ namespace OpenMetaverse /// public class SoundTriggerEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly UUID m_SoundID; - private readonly UUID m_OwnerID; - private readonly UUID m_ObjectID; - private readonly UUID m_ParentID; - private readonly float m_Gain; - private readonly ulong m_RegionHandle; - private readonly Vector3 m_Position; - /// Simulator where the event originated - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// Get the sound asset id - public UUID SoundID => m_SoundID; + public UUID SoundID { get; } /// Get the ID of the owner - public UUID OwnerID => m_OwnerID; + public UUID OwnerID { get; } /// Get the ID of the Object - public UUID ObjectID => m_ObjectID; + public UUID ObjectID { get; } /// Get the ID of the objects parent - public UUID ParentID => m_ParentID; + public UUID ParentID { get; } /// Get the volume level - public float Gain => m_Gain; + public float Gain { get; } /// Get the regionhandle - public ulong RegionHandle => m_RegionHandle; + public ulong RegionHandle { get; } /// Get the source position - public Vector3 Position => m_Position; + public Vector3 Position { get; } /// /// Construct a new instance of the SoundTriggerEventArgs class @@ -444,14 +431,14 @@ namespace OpenMetaverse /// The source position public SoundTriggerEventArgs(Simulator sim, UUID soundID, UUID ownerID, UUID objectID, UUID parentID, float gain, ulong regionHandle, Vector3 position) { - m_Simulator = sim; - m_SoundID = soundID; - m_OwnerID = ownerID; - m_ObjectID = objectID; - m_ParentID = parentID; - m_Gain = gain; - m_RegionHandle = regionHandle; - m_Position = position; + Simulator = sim; + SoundID = soundID; + OwnerID = ownerID; + ObjectID = objectID; + ParentID = parentID; + Gain = gain; + RegionHandle = regionHandle; + Position = position; } } @@ -474,22 +461,17 @@ namespace OpenMetaverse /// public class PreloadSoundEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly UUID m_SoundID; - private readonly UUID m_OwnerID; - private readonly UUID m_ObjectID; - /// Simulator where the event originated - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// Get the sound asset id - public UUID SoundID => m_SoundID; + public UUID SoundID { get; } /// Get the ID of the owner - public UUID OwnerID => m_OwnerID; + public UUID OwnerID { get; } /// Get the ID of the Object - public UUID ObjectID => m_ObjectID; + public UUID ObjectID { get; } /// /// Construct a new instance of the PreloadSoundEventArgs class @@ -500,10 +482,10 @@ namespace OpenMetaverse /// The ID of the object public PreloadSoundEventArgs(Simulator sim, UUID soundID, UUID ownerID, UUID objectID) { - m_Simulator = sim; - m_SoundID = soundID; - m_OwnerID = ownerID; - m_ObjectID = objectID; + Simulator = sim; + SoundID = soundID; + OwnerID = ownerID; + ObjectID = objectID; } } #endregion diff --git a/Assets/Plugins/LibreMetaverse/Sounds.cs b/Assets/Plugins/LibreMetaverse/Sounds.cs index 32aacf0..6c0f13b 100644 --- a/Assets/Plugins/LibreMetaverse/Sounds.cs +++ b/Assets/Plugins/LibreMetaverse/Sounds.cs @@ -24,7 +24,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; using System.Reflection; using System.Collections.Generic; diff --git a/Assets/Plugins/LibreMetaverse/TerrainManager.cs b/Assets/Plugins/LibreMetaverse/TerrainManager.cs index b8452f1..8b1c6cf 100644 --- a/Assets/Plugins/LibreMetaverse/TerrainManager.cs +++ b/Assets/Plugins/LibreMetaverse/TerrainManager.cs @@ -193,34 +193,28 @@ namespace OpenMetaverse // Provides data for LandPatchReceived public class LandPatchReceivedEventArgs : EventArgs { - private readonly Simulator m_Simulator; - private readonly int m_X; - private readonly int m_Y; - private readonly int m_PatchSize; - private readonly float[] m_HeightMap; - /// Simulator from that sent tha data - public Simulator Simulator => m_Simulator; + public Simulator Simulator { get; } /// Sim coordinate of the patch - public int X => m_X; + public int X { get; } /// Sim coordinate of the patch - public int Y => m_Y; + public int Y { get; } /// Size of tha patch - public int PatchSize => m_PatchSize; + public int PatchSize { get; } /// Heightmap for the patch - public float[] HeightMap => m_HeightMap; + public float[] HeightMap { get; } public LandPatchReceivedEventArgs(Simulator simulator, int x, int y, int patchSize, float[] heightMap) { - m_Simulator = simulator; - m_X = x; - m_Y = y; - m_PatchSize = patchSize; - m_HeightMap = heightMap; + Simulator = simulator; + X = x; + Y = y; + PatchSize = patchSize; + HeightMap = heightMap; } } #endregion diff --git a/Assets/Plugins/LibreMetaverse/UDPPacketBuffer.cs b/Assets/Plugins/LibreMetaverse/UDPPacketBuffer.cs index 7574bf0..50659b6 100644 --- a/Assets/Plugins/LibreMetaverse/UDPPacketBuffer.cs +++ b/Assets/Plugins/LibreMetaverse/UDPPacketBuffer.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System; using System.Net; namespace OpenMetaverse diff --git a/Assets/Plugins/LibreMetaverse/Voice/TCPPipe.cs b/Assets/Plugins/LibreMetaverse/Voice/TCPPipe.cs index 59553a9..5a93678 100644 --- a/Assets/Plugins/LibreMetaverse/Voice/TCPPipe.cs +++ b/Assets/Plugins/LibreMetaverse/Voice/TCPPipe.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -28,13 +29,13 @@ using System; using System.Net; using System.Net.Sockets; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public class TCPPipe { protected class SocketPacket { - public System.Net.Sockets.Socket TCPSocket; + public Socket TCPSocket; public byte[] DataBuffer = new byte[1]; } @@ -47,12 +48,9 @@ namespace OpenMetaverse.Voice protected Socket _TCPSocket; protected IAsyncResult _Result; protected AsyncCallback _Callback; - protected string _Buffer = String.Empty; + protected string _Buffer = string.Empty; - public bool Connected - { - get { return _TCPSocket != null && _TCPSocket.Connected; } - } + public bool Connected => _TCPSocket != null && _TCPSocket.Connected; public SocketException Connect(string address, int port) { @@ -117,8 +115,10 @@ namespace OpenMetaverse.Voice try { if (_Callback == null) _Callback = new AsyncCallback(OnDataReceived); - SocketPacket packet = new SocketPacket(); - packet.TCPSocket = _TCPSocket; + SocketPacket packet = new SocketPacket + { + TCPSocket = _TCPSocket + }; _Result = _TCPSocket.BeginReceive(packet.DataBuffer, 0, packet.DataBuffer.Length, SocketFlags.None, _Callback, packet); } catch (SocketException se) @@ -127,8 +127,8 @@ namespace OpenMetaverse.Voice } } - static char[] splitNull = { '\0' }; - static string[] splitLines = { "\r", "\n", "\r\n" }; + static readonly char[] splitNull = { '\0' }; + static readonly string[] splitLines = { "\r", "\n", "\r\n" }; void ReceiveData(string data) { @@ -159,7 +159,7 @@ namespace OpenMetaverse.Voice char[] chars = new char[end + 1]; System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder(); d.GetChars(packet.DataBuffer, 0, end, chars, 0); - System.String data = new System.String(chars); + string data = new string(chars); ReceiveData(data); WaitForData(); } @@ -171,8 +171,9 @@ namespace OpenMetaverse.Voice { if (!_TCPSocket.Connected) { - if (OnDisconnected != null) - OnDisconnected(se); + // if (OnDisconnected != null) + // OnDisconnected(se); + OnDisconnected?.Invoke(se); } } } diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceAccount.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceAccount.cs index 603fed3..4eaff06 100644 --- a/Assets/Plugins/LibreMetaverse/Voice/VoiceAccount.cs +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceAccount.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -24,11 +25,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using System.Collections.Generic; using System.Text; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public partial class VoiceGateway { diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceAux.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceAux.cs index afbcc3b..02c781e 100644 --- a/Assets/Plugins/LibreMetaverse/Voice/VoiceAux.cs +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceAux.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -24,11 +25,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using System.Collections.Generic; -using System.Text; - -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public partial class VoiceGateway { diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceConnector.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceConnector.cs index 55528d4..7724166 100644 --- a/Assets/Plugins/LibreMetaverse/Voice/VoiceConnector.cs +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceConnector.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -24,11 +25,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; -using System.Collections.Generic; using System.Text; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public partial class VoiceGateway { diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceControl.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceControl.cs index 328a2f3..a8af6e3 100644 --- a/Assets/Plugins/LibreMetaverse/Voice/VoiceControl.cs +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceControl.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -27,15 +28,14 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.IO; +using System.Runtime.InteropServices; using System.Threading; - using OpenMetaverse; using OpenMetaverse.StructuredData; +using OpenMetaverse.Http; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public partial class VoiceGateway : IDisposable { @@ -71,7 +71,7 @@ namespace OpenMetaverse.Voice private string spatialCredentials; // Session management - private Dictionary sessions; + private readonly Dictionary sessions; private VoiceSession spatialSession; private Uri currentParcelCap; private Uri nextParcelCap; @@ -82,21 +82,21 @@ namespace OpenMetaverse.Voice private CancellationTokenSource posTokenSource; private ManualResetEvent posRestart; public GridClient Client; - private VoicePosition position; + private readonly VoicePosition position; private Vector3d oldPosition; private Vector3d oldAt; // Audio interfaces - private List inputDevices; /// /// List of audio input devices /// - public List CaptureDevices { get { return inputDevices; } } - private List outputDevices; + public List CaptureDevices { get; private set; } + /// /// List of audio output devices /// - public List PlaybackDevices { get { return outputDevices; } } + public List PlaybackDevices { get; private set; } + private string currentCaptureDevice; private string currentPlaybackDevice; private bool testing = false; @@ -110,15 +110,17 @@ namespace OpenMetaverse.Voice public VoiceGateway(GridClient c) { - Random rand = new Random(); + var rand = new Random(); daemonPort = rand.Next(34000, 44000); Client = c; sessions = new Dictionary(); - position = new VoicePosition(); - position.UpOrientation = new Vector3d(0.0, 1.0, 0.0); - position.Velocity = new Vector3d(0.0, 0.0, 0.0); + position = new VoicePosition + { + UpOrientation = new Vector3d(0.0, 1.0, 0.0), + Velocity = new Vector3d(0.0, 0.0, 0.0) + }; oldPosition = new Vector3d(0, 0, 0); oldAt = new Vector3d(1, 0, 0); @@ -140,60 +142,48 @@ namespace OpenMetaverse.Voice } posTokenSource = new CancellationTokenSource(); - posThread = new Thread(new ThreadStart(PositionThreadBody)); - posThread.Name = "VoicePositionUpdate"; - posThread.IsBackground = true; + posThread = new Thread(PositionThreadBody) + { + Name = "VoicePositionUpdate", + IsBackground = true + }; posRestart = new ManualResetEvent(false); posThread.Start(); - Client.Network.EventQueueRunning += new EventHandler(Network_EventQueueRunning); + Client.Network.EventQueueRunning += Network_EventQueueRunning; // Connection events - OnDaemonRunning += - new VoiceGateway.DaemonRunningCallback(connector_OnDaemonRunning); - OnDaemonCouldntRun += - new VoiceGateway.DaemonCouldntRunCallback(connector_OnDaemonCouldntRun); - OnConnectorCreateResponse += - new EventHandler(connector_OnConnectorCreateResponse); - OnDaemonConnected += - new DaemonConnectedCallback(connector_OnDaemonConnected); - OnDaemonCouldntConnect += - new DaemonCouldntConnectCallback(connector_OnDaemonCouldntConnect); - OnAuxAudioPropertiesEvent += - new EventHandler(connector_OnAuxAudioPropertiesEvent); + OnDaemonRunning += connector_OnDaemonRunning; + OnDaemonCouldntRun += connector_OnDaemonCouldntRun; + OnConnectorCreateResponse += connector_OnConnectorCreateResponse; + OnDaemonConnected += connector_OnDaemonConnected; + OnDaemonCouldntConnect += connector_OnDaemonCouldntConnect; + OnAuxAudioPropertiesEvent += connector_OnAuxAudioPropertiesEvent; // Session events - OnSessionStateChangeEvent += - new EventHandler(connector_OnSessionStateChangeEvent); - OnSessionAddedEvent += - new EventHandler(connector_OnSessionAddedEvent); + OnSessionStateChangeEvent += connector_OnSessionStateChangeEvent; + OnSessionAddedEvent += connector_OnSessionAddedEvent; // Session Participants events - OnSessionParticipantUpdatedEvent += - new EventHandler(connector_OnSessionParticipantUpdatedEvent); - OnSessionParticipantAddedEvent += - new EventHandler(connector_OnSessionParticipantAddedEvent); + OnSessionParticipantUpdatedEvent += connector_OnSessionParticipantUpdatedEvent; + OnSessionParticipantAddedEvent += connector_OnSessionParticipantAddedEvent; // Device events - OnAuxGetCaptureDevicesResponse += - new EventHandler(connector_OnAuxGetCaptureDevicesResponse); - OnAuxGetRenderDevicesResponse += - new EventHandler(connector_OnAuxGetRenderDevicesResponse); + OnAuxGetCaptureDevicesResponse += connector_OnAuxGetCaptureDevicesResponse; + OnAuxGetRenderDevicesResponse += connector_OnAuxGetRenderDevicesResponse; // Generic status response - OnVoiceResponse += new EventHandler(connector_OnVoiceResponse); + OnVoiceResponse += connector_OnVoiceResponse; // Account events - OnAccountLoginResponse += - new EventHandler(connector_OnAccountLoginResponse); + OnAccountLoginResponse += connector_OnAccountLoginResponse; Logger.Log("Voice initialized", Helpers.LogLevel.Info); // If voice provisioning capability is already available, // proceed with voice startup. Otherwise the EventQueueRunning // event will do it. - System.Uri vCap = - Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest"); + var vCap = Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest"); if (vCap != null) RequestVoiceProvision(vCap); @@ -205,55 +195,39 @@ namespace OpenMetaverse.Voice /// /// /// ///If something goes wrong, we log it. - void connector_OnVoiceResponse(object sender, VoiceGateway.VoiceResponseEventArgs e) + void connector_OnVoiceResponse(object sender, VoiceResponseEventArgs e) { - if (e.StatusCode == 0) - return; - - Logger.Log(e.Message + " on " + sender as string, Helpers.LogLevel.Error); + if (e.StatusCode == 0) { return; } + Logger.Log($"{e.Message} on {sender}", Helpers.LogLevel.Error); } public void Stop() { - Client.Network.EventQueueRunning -= new EventHandler(Network_EventQueueRunning); + Client.Network.EventQueueRunning -= Network_EventQueueRunning; // Connection events - OnDaemonRunning -= - new VoiceGateway.DaemonRunningCallback(connector_OnDaemonRunning); - OnDaemonCouldntRun -= - new VoiceGateway.DaemonCouldntRunCallback(connector_OnDaemonCouldntRun); - OnConnectorCreateResponse -= - new EventHandler(connector_OnConnectorCreateResponse); - OnDaemonConnected -= - new VoiceGateway.DaemonConnectedCallback(connector_OnDaemonConnected); - OnDaemonCouldntConnect -= - new VoiceGateway.DaemonCouldntConnectCallback(connector_OnDaemonCouldntConnect); - OnAuxAudioPropertiesEvent -= - new EventHandler(connector_OnAuxAudioPropertiesEvent); + OnDaemonRunning -= connector_OnDaemonRunning; + OnDaemonCouldntRun -= connector_OnDaemonCouldntRun; + OnConnectorCreateResponse -= connector_OnConnectorCreateResponse; + OnDaemonConnected -= connector_OnDaemonConnected; + OnDaemonCouldntConnect -= connector_OnDaemonCouldntConnect; + OnAuxAudioPropertiesEvent -= connector_OnAuxAudioPropertiesEvent; // Session events - OnSessionStateChangeEvent -= - new EventHandler(connector_OnSessionStateChangeEvent); - OnSessionAddedEvent -= - new EventHandler(connector_OnSessionAddedEvent); + OnSessionStateChangeEvent -= connector_OnSessionStateChangeEvent; + OnSessionAddedEvent -= connector_OnSessionAddedEvent; // Session Participants events - OnSessionParticipantUpdatedEvent -= - new EventHandler(connector_OnSessionParticipantUpdatedEvent); - OnSessionParticipantAddedEvent -= - new EventHandler(connector_OnSessionParticipantAddedEvent); - OnSessionParticipantRemovedEvent -= - new EventHandler(connector_OnSessionParticipantRemovedEvent); + OnSessionParticipantUpdatedEvent -= connector_OnSessionParticipantUpdatedEvent; + OnSessionParticipantAddedEvent -= connector_OnSessionParticipantAddedEvent; + OnSessionParticipantRemovedEvent -= connector_OnSessionParticipantRemovedEvent; // Tuning events - OnAuxGetCaptureDevicesResponse -= - new EventHandler(connector_OnAuxGetCaptureDevicesResponse); - OnAuxGetRenderDevicesResponse -= - new EventHandler(connector_OnAuxGetRenderDevicesResponse); + OnAuxGetCaptureDevicesResponse -= connector_OnAuxGetCaptureDevicesResponse; + OnAuxGetRenderDevicesResponse -= connector_OnAuxGetRenderDevicesResponse; // Account events - OnAccountLoginResponse -= - new EventHandler(connector_OnAccountLoginResponse); + OnAccountLoginResponse -= connector_OnAccountLoginResponse; // Stop the background thread if (posThread != null) @@ -267,10 +241,9 @@ namespace OpenMetaverse.Voice } // Close all sessions - foreach (VoiceSession s in sessions.Values) + foreach (var s in sessions.Values) { - if (OnSessionRemove != null) - OnSessionRemove(s, EventArgs.Empty); + OnSessionRemove?.Invoke(s, EventArgs.Empty); s.Close(); } @@ -309,56 +282,46 @@ namespace OpenMetaverse.Voice internal string GetVoiceDaemonPath() { - string myDir = + var myDir = Path.GetDirectoryName( (System.Reflection.Assembly.GetEntryAssembly() ?? typeof (VoiceGateway).Assembly).Location); - if (Environment.OSVersion.Platform != PlatformID.MacOSX && - Environment.OSVersion.Platform != PlatformID.Unix) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - string localDaemon = Path.Combine(myDir, Path.Combine("voice", "SLVoice.exe")); + if (myDir != null) + { + var localDaemon = Path.Combine(myDir, Path.Combine("voice", "SLVoice.exe")); - if (File.Exists(localDaemon)) - return localDaemon; - - string progFiles; - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ProgramFiles(x86)"))) - { - progFiles = Environment.GetEnvironmentVariable("ProgramFiles(x86)"); - } - else - { - progFiles = Environment.GetEnvironmentVariable("ProgramFiles"); + if (File.Exists(localDaemon)) + return localDaemon; } - if (System.IO.File.Exists(Path.Combine(progFiles, @"SecondLife" + Path.DirectorySeparatorChar + @"SLVoice.exe"))) - { - return Path.Combine(progFiles, @"SecondLife" + Path.DirectorySeparatorChar + @"SLVoice.exe"); - } - - return Path.Combine(myDir, @"SLVoice.exe"); + var progFiles = Environment.GetEnvironmentVariable( + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ProgramFiles(x86)")) + ? "ProgramFiles(x86)" : "ProgramFiles"); + return progFiles != null && File.Exists(Path.Combine(progFiles, @"SecondLife" + Path.DirectorySeparatorChar + @"SLVoice.exe")) + ? Path.Combine(progFiles, @"SecondLife" + Path.DirectorySeparatorChar + @"SLVoice.exe") + : Path.Combine(myDir, @"SLVoice.exe"); } - else + + if (myDir != null) { - string localDaemon = Path.Combine(myDir, Path.Combine("voice", "SLVoice")); - - if (File.Exists(localDaemon)) - return localDaemon; - - return Path.Combine(myDir,"SLVoice"); + var localDaemon = Path.Combine(myDir, Path.Combine("voice", "SLVoice")); + return File.Exists(localDaemon) ? localDaemon : Path.Combine(myDir,"SLVoice"); } + + return string.Empty; } - void RequestVoiceProvision(System.Uri cap) + void RequestVoiceProvision(Uri cap) { - Http.CapsClient capClient = new Http.CapsClient(cap, "ReqVoiceProvision"); + var capClient = new CapsClient(cap, "ReqVoiceProvision"); capClient.OnComplete += cClient_OnComplete; - OSD postData = new OSD(); // STEP 0 Logger.Log("Requesting voice capability", Helpers.LogLevel.Info); - capClient.BeginGetResponse(postData, OSDFormat.Xml, 10000); + capClient.PostRequestAsync(new OSD(), OSDFormat.Xml, 10000); } /// @@ -382,8 +345,7 @@ namespace OpenMetaverse.Voice // 5. Create session // Get the voice provisioning data - System.Uri vCap = - Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest"); + var vCap = Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest"); // Do we have voice capability? if (vCap == null) @@ -409,9 +371,8 @@ namespace OpenMetaverse.Voice void connector_OnSessionParticipantUpdatedEvent(object sender, ParticipantUpdatedEventArgs e) { - VoiceSession s = FindSession(e.SessionHandle, false); - if (s == null) return; - s.ParticipantUpdate(e.URI, e.IsMuted, e.IsSpeaking, e.Volume, e.Energy); + var s = FindSession(e.SessionHandle, false); + s?.ParticipantUpdate(e.URI, e.IsMuted, e.IsSpeaking, e.Volume, e.Energy); } public string SIPFromUUID(UUID id) @@ -435,7 +396,7 @@ namespace OpenMetaverse.Voice // Base64 encode and replace the pieces of base64 that are less compatible // with e-mail local-parts. // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" - byte[] encbuff = id.GetBytes(); + var encbuff = id.GetBytes(); result += Convert.ToBase64String(encbuff); result = result.Replace('+', '-'); result = result.Replace('/', '_'); @@ -445,7 +406,7 @@ namespace OpenMetaverse.Voice void connector_OnSessionParticipantAddedEvent(object sender, ParticipantAddedEventArgs e) { - VoiceSession s = FindSession(e.SessionHandle, false); + var s = FindSession(e.SessionHandle, false); if (s == null) { Logger.Log("Orphan participant", Helpers.LogLevel.Error); @@ -456,9 +417,8 @@ namespace OpenMetaverse.Voice void connector_OnSessionParticipantRemovedEvent(object sender, ParticipantRemovedEventArgs e) { - VoiceSession s = FindSession(e.SessionHandle, false); - if (s == null) return; - s.RemoveParticipant(e.URI); + var s = FindSession(e.SessionHandle, false); + s?.RemoveParticipant(e.URI); } #endregion @@ -468,14 +428,13 @@ namespace OpenMetaverse.Voice sessionHandle = e.SessionHandle; // Create our session context. - VoiceSession s = FindSession(sessionHandle, true); + var s = FindSession(sessionHandle, true); s.RegionName = regionName; spatialSession = s; // Tell any user-facing code. - if (OnSessionCreate != null) - OnSessionCreate(s, null); + OnSessionCreate?.Invoke(s, null); Logger.Log("Added voice session in " + regionName, Helpers.LogLevel.Info); } @@ -489,7 +448,7 @@ namespace OpenMetaverse.Voice switch (e.State) { - case VoiceGateway.SessionState.Connected: + case SessionState.Connected: s = FindSession(e.SessionHandle, true); sessionHandle = e.SessionHandle; s.RegionName = regionName; @@ -497,11 +456,10 @@ namespace OpenMetaverse.Voice Logger.Log("Voice connected in " + regionName, Helpers.LogLevel.Info); // Tell any user-facing code. - if (OnSessionCreate != null) - OnSessionCreate(s, null); + OnSessionCreate?.Invoke(s, null); break; - case VoiceGateway.SessionState.Disconnected: + case SessionState.Disconnected: s = FindSession(sessionHandle, false); sessions.Remove(sessionHandle); @@ -510,8 +468,7 @@ namespace OpenMetaverse.Voice Logger.Log("Voice disconnected in " + s.RegionName, Helpers.LogLevel.Info); // Inform interested parties - if (OnSessionRemove != null) - OnSessionRemove(s, null); + OnSessionRemove?.Invoke(s, null); if (s == spatialSession) spatialSession = null; @@ -526,6 +483,20 @@ namespace OpenMetaverse.Voice RequestParcelInfo(currentParcelCap); } break; + case SessionState.Idle: + break; + case SessionState.Answering: + break; + case SessionState.InProgress: + break; + case SessionState.Hold: + break; + case SessionState.Refer: + break; + case SessionState.Ringing: + break; + default: + throw new ArgumentOutOfRangeException(); } @@ -544,7 +515,7 @@ namespace OpenMetaverse.Voice ReportConnectionState(ConnectionState.AccountLogin); // Clean up spatial pointers. - VoiceSession s = sessions[sessionHandle]; + var s = sessions[sessionHandle]; if (s.IsSpatial) { spatialSession = null; @@ -555,8 +526,7 @@ namespace OpenMetaverse.Voice sessions.Remove(sessionHandle); // Let any user-facing code clean up. - if (OnSessionRemove != null) - OnSessionRemove(s, null); + OnSessionRemove?.Invoke(s, null); // Tell SLVoice to clean it up as well. SessionTerminate(sessionHandle); @@ -574,7 +544,7 @@ namespace OpenMetaverse.Voice if (!make) return null; // Create a new session and add it to the sessions list. - VoiceSession s = new VoiceSession(this, sessionHandle); + var s = new VoiceSession(this, sessionHandle); // Turn on position updating for spatial sessions // (For now, only spatial sessions are supported) @@ -592,17 +562,14 @@ namespace OpenMetaverse.Voice void connector_OnAuxAudioPropertiesEvent(object sender, AudioPropertiesEventArgs e) { - if (OnVoiceMicTest != null) - OnVoiceMicTest(e.MicEnergy); + OnVoiceMicTest?.Invoke(e.MicEnergy); } #endregion private void ReportConnectionState(ConnectionState s) { - if (OnVoiceConnectionChange == null) return; - - OnVoiceConnectionChange(s); + OnVoiceConnectionChange?.Invoke(s); } /// @@ -611,8 +578,8 @@ namespace OpenMetaverse.Voice /// /// /// - void cClient_OnComplete(OpenMetaverse.Http.CapsClient client, - OpenMetaverse.StructuredData.OSD result, + void cClient_OnComplete(CapsClient client, + OSD result, Exception error) { if (error != null) @@ -624,25 +591,26 @@ namespace OpenMetaverse.Voice Logger.Log("Voice provisioned", Helpers.LogLevel.Info); ReportConnectionState(ConnectionState.Provisioned); - OpenMetaverse.StructuredData.OSDMap pMap = result as OpenMetaverse.StructuredData.OSDMap; - // We can get back 4 interesting values: // voice_sip_uri_hostname // voice_account_server_name (actually a full URI) // username // password - if (pMap.ContainsKey("voice_sip_uri_hostname")) - sipServer = pMap["voice_sip_uri_hostname"].AsString(); - if (pMap.ContainsKey("voice_account_server_name")) - acctServer = pMap["voice_account_server_name"].AsString(); - voiceUser = pMap["username"].AsString(); - voicePassword = pMap["password"].AsString(); + if (result is OSDMap pMap) + { + if (pMap.ContainsKey("voice_sip_uri_hostname")) + sipServer = pMap["voice_sip_uri_hostname"].AsString(); + if (pMap.ContainsKey("voice_account_server_name")) + acctServer = pMap["voice_account_server_name"].AsString(); + voiceUser = pMap["username"].AsString(); + voicePassword = pMap["password"].AsString(); + } // Start the SLVoice daemon slvoicePath = GetVoiceDaemonPath(); // Test if the executable exists - if (!System.IO.File.Exists(slvoicePath)) + if (!File.Exists(slvoicePath)) { Logger.Log("SLVoice is missing", Helpers.LogLevel.Error); return; @@ -669,7 +637,7 @@ namespace OpenMetaverse.Voice void connector_OnDaemonRunning() { OnDaemonRunning -= - new VoiceGateway.DaemonRunningCallback(connector_OnDaemonRunning); + connector_OnDaemonRunning; Logger.Log("Daemon started", Helpers.LogLevel.Info); ReportConnectionState(ConnectionState.DaemonStarted); @@ -688,8 +656,8 @@ namespace OpenMetaverse.Voice ReportConnectionState(ConnectionState.DaemonConnected); // The connector is what does the logging. - VoiceGateway.VoiceLoggingSettings vLog = - new VoiceGateway.VoiceLoggingSettings(); + var vLog = + new VoiceLoggingSettings(); #if DEBUG_VOICE vLog.Enabled = true; @@ -698,7 +666,7 @@ namespace OpenMetaverse.Voice vLog.LogLevel = 4; #endif // STEP 3 - int reqId = ConnectorCreate( + var reqId = ConnectorCreate( "V2 SDK", // Magic value keeps SLVoice happy acctServer, // Account manager server 30000, 30099, // port range @@ -714,7 +682,7 @@ namespace OpenMetaverse.Voice /// void connector_OnConnectorCreateResponse( object sender, - VoiceGateway.VoiceConnectorEventArgs e) + VoiceConnectorEventArgs e) { Logger.Log("Voice daemon protocol started " + e.Message, Helpers.LogLevel.Info); @@ -737,9 +705,9 @@ namespace OpenMetaverse.Voice void connector_OnAccountLoginResponse( object sender, - VoiceGateway.VoiceAccountEventArgs e) + VoiceAccountEventArgs e) { - Logger.Log("Account Login " + e.Message, Helpers.LogLevel.Info); + Logger.Log($"Account Login {e.Message}", Helpers.LogLevel.Info); accountHandle = e.AccountHandle; ReportConnectionState(ConnectionState.AccountLogin); ParcelChanged(); @@ -751,9 +719,9 @@ namespace OpenMetaverse.Voice /// void connector_OnAuxGetRenderDevicesResponse( object sender, - VoiceGateway.VoiceDevicesEventArgs e) + VoiceDevicesEventArgs e) { - outputDevices = e.Devices; + PlaybackDevices = e.Devices; currentPlaybackDevice = e.CurrentDevice; } @@ -762,15 +730,15 @@ namespace OpenMetaverse.Voice /// void connector_OnAuxGetCaptureDevicesResponse( object sender, - VoiceGateway.VoiceDevicesEventArgs e) + VoiceDevicesEventArgs e) { - inputDevices = e.Devices; + CaptureDevices = e.Devices; currentCaptureDevice = e.CurrentDevice; } public string CurrentCaptureDevice { - get { return currentCaptureDevice; } + get => currentCaptureDevice; set { currentCaptureDevice = value; @@ -779,7 +747,7 @@ namespace OpenMetaverse.Voice } public string PlaybackDevice { - get { return currentPlaybackDevice; } + get => currentPlaybackDevice; set { currentPlaybackDevice = value; @@ -789,33 +757,21 @@ namespace OpenMetaverse.Voice public int MicLevel { - set - { - ConnectorSetLocalMicVolume(connectionHandle, value); - } + set => ConnectorSetLocalMicVolume(connectionHandle, value); } public int SpkrLevel { - set - { - ConnectorSetLocalSpeakerVolume(connectionHandle, value); - } + set => ConnectorSetLocalSpeakerVolume(connectionHandle, value); } public bool MicMute { - set - { - ConnectorMuteLocalMic(connectionHandle, value); - } + set => ConnectorMuteLocalMic(connectionHandle, value); } public bool SpkrMute { - set - { - ConnectorMuteLocalSpeaker(connectionHandle, value); - } + set => ConnectorMuteLocalSpeaker(connectionHandle, value); } /// @@ -823,7 +779,7 @@ namespace OpenMetaverse.Voice /// public bool TestMode { - get { return testing; } + get => testing; set { testing = value; @@ -855,8 +811,8 @@ namespace OpenMetaverse.Voice internal void ParcelChanged() { // Get the capability for this parcel. - Caps c = Client.Network.CurrentSim.Caps; - System.Uri pCap = c.CapabilityURI("ParcelVoiceInfoRequest"); + var c = Client.Network.CurrentSim.Caps; + var pCap = c.CapabilityURI("ParcelVoiceInfoRequest"); if (pCap == null) { @@ -875,7 +831,7 @@ namespace OpenMetaverse.Voice RequestParcelInfo(pCap); } - private OpenMetaverse.Http.CapsClient parcelCap; + private CapsClient parcelCap; /// /// Request info from a parcel capability Uri. @@ -886,13 +842,12 @@ namespace OpenMetaverse.Voice { Logger.Log("Requesting region voice info", Helpers.LogLevel.Info); - parcelCap = new OpenMetaverse.Http.CapsClient(cap, "RequestParcelInfo"); + parcelCap = new CapsClient(cap, "RequestParcelInfo"); parcelCap.OnComplete += pCap_OnComplete; - OSD postData = new OSD(); currentParcelCap = cap; - parcelCap.BeginGetResponse(postData, OSDFormat.Xml, 10000); + parcelCap.PostRequestAsync(new OSD(), OSDFormat.Xml, 10000); } /// @@ -901,12 +856,11 @@ namespace OpenMetaverse.Voice /// /// /// - void pCap_OnComplete(OpenMetaverse.Http.CapsClient client, - OpenMetaverse.StructuredData.OSD result, + void pCap_OnComplete(CapsClient client, + OSD result, Exception error) { - parcelCap.OnComplete -= - new OpenMetaverse.Http.CapsClient.CompleteCallback(pCap_OnComplete); + parcelCap.OnComplete -= pCap_OnComplete; parcelCap = null; if (error != null) @@ -915,20 +869,21 @@ namespace OpenMetaverse.Voice return; } - OpenMetaverse.StructuredData.OSDMap pMap = result as OpenMetaverse.StructuredData.OSDMap; - - regionName = pMap["region_name"].AsString(); - ReportConnectionState(ConnectionState.RegionCapAvailable); - - if (pMap.ContainsKey("voice_credentials")) + if (result is OSDMap pMap) { - OpenMetaverse.StructuredData.OSDMap cred = - pMap["voice_credentials"] as OpenMetaverse.StructuredData.OSDMap; + regionName = pMap["region_name"].AsString(); + ReportConnectionState(ConnectionState.RegionCapAvailable); - if (cred.ContainsKey("channel_uri")) - spatialUri = cred["channel_uri"].AsString(); - if (cred.ContainsKey("channel_credentials")) - spatialCredentials = cred["channel_credentials"].AsString(); + if (pMap.ContainsKey("voice_credentials")) + { + var cred = + pMap["voice_credentials"] as OSDMap; + + if (cred.ContainsKey("channel_uri")) + spatialUri = cred["channel_uri"].AsString(); + if (cred.ContainsKey("channel_credentials")) + spatialCredentials = cred["channel_credentials"].AsString(); + } } if (string.IsNullOrEmpty(spatialUri)) @@ -940,7 +895,7 @@ namespace OpenMetaverse.Voice Logger.Log("Voice connecting for region " + regionName, Helpers.LogLevel.Info); // STEP 5 - int reqId = SessionCreate( + var reqId = SessionCreate( accountHandle, spatialUri, // uri "", // Channel name seems to be always null @@ -962,7 +917,7 @@ namespace OpenMetaverse.Voice internal void UpdatePosition(AgentManager self) { // Get position in Global coordinates - Vector3d OMVpos = new Vector3d(self.GlobalPosition); + var OMVpos = new Vector3d(self.GlobalPosition); // Do not send trivial updates. if (OMVpos.ApproxEquals(oldPosition, 1.0)) @@ -979,7 +934,7 @@ namespace OpenMetaverse.Voice // Get azimuth from the facing Quaternion. // By definition, facing.W = Cos( angle/2 ) - double angle = 2.0 * Math.Acos(self.Movement.BodyRotation.W); + var angle = 2.0 * Math.Acos(self.Movement.BodyRotation.W); position.LeftOrientation = new Vector3d(-1.0, 0.0, 0.0); position.AtOrientation = new Vector3d((float)Math.Acos(angle), 0.0, -(float)Math.Asin(angle)); @@ -1005,7 +960,7 @@ namespace OpenMetaverse.Voice private void PositionThreadBody() { var token = posTokenSource.Token; - while (true) + while (!token.IsCancellationRequested) { posRestart.WaitOne(); token.ThrowIfCancellationRequested(); diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceDefinitions.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceDefinitions.cs index 1bcce53..7d7f8c8 100644 --- a/Assets/Plugins/LibreMetaverse/Voice/VoiceDefinitions.cs +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceDefinitions.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -26,10 +27,10 @@ using System; using System.Collections.Generic; -using System.Xml; using System.Xml.Serialization; +using OpenMetaverse; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public partial class VoiceGateway { @@ -424,16 +425,14 @@ namespace OpenMetaverse.Voice #region Connector Delegates public class VoiceConnectorEventArgs : VoiceResponseEventArgs { - private readonly string m_Version; - private readonly string m_ConnectorHandle; - public string Version { get { return m_Version; } } - public string Handle { get { return m_ConnectorHandle; } } + public string Version { get; } + public string Handle { get; } public VoiceConnectorEventArgs(int rcode, int scode, string text, string version, string handle) : base(ResponseType.ConnectorCreate, rcode, scode, text) { - m_Version = version; - m_ConnectorHandle = handle; + Version = version; + Handle = handle; } } @@ -443,16 +442,14 @@ namespace OpenMetaverse.Voice #region Aux Event Args public class VoiceDevicesEventArgs : VoiceResponseEventArgs { - private readonly string m_CurrentDevice; - private readonly List m_Available; - public string CurrentDevice { get { return m_CurrentDevice; } } - public List Devices { get { return m_Available; } } + public string CurrentDevice { get; } + public List Devices { get; } public VoiceDevicesEventArgs(ResponseType type, int rcode, int scode, string text, string current, List avail) : base(type, rcode, scode, text) { - m_CurrentDevice = current; - m_Available = avail; + CurrentDevice = current; + Devices = avail; } } @@ -478,13 +475,12 @@ namespace OpenMetaverse.Voice #region Account Event Args public class VoiceAccountEventArgs : VoiceResponseEventArgs { - private readonly string m_AccountHandle; - public string AccountHandle { get { return m_AccountHandle; } } + public string AccountHandle { get; } public VoiceAccountEventArgs(int rcode, int scode, string text, string ahandle) : base(ResponseType.AccountLogin, rcode, scode, text) { - this.m_AccountHandle = ahandle; + this.AccountHandle = ahandle; } } @@ -612,7 +608,7 @@ namespace OpenMetaverse.Voice public string RequestId; [XmlAttribute("action")] public string Action; - public string ReturnCode; + public int ReturnCode; public VoiceResponseResults Results; public VoiceInputXml InputXml; } @@ -630,7 +626,7 @@ namespace OpenMetaverse.Voice public class VoiceResponseResults { public string VersionID; - public string StatusCode; + public int StatusCode; public string StatusString; public string ConnectorHandle; public string AccountHandle; diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceGateway.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceGateway.cs index 202792b..90e8b9b 100644 --- a/Assets/Plugins/LibreMetaverse/Voice/VoiceGateway.cs +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceGateway.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -31,8 +32,9 @@ using System.Net.Sockets; using System.Diagnostics; using System.Threading; using System.Text; +using OpenMetaverse; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public partial class VoiceGateway { @@ -50,9 +52,9 @@ namespace OpenMetaverse.Voice public event DaemonDisconnectedCallback OnDaemonDisconnected; public event DaemonCouldntConnectCallback OnDaemonCouldntConnect; - public bool DaemonIsRunning { get { return daemonIsRunning; } } - public bool DaemonIsConnected { get { return daemonIsConnected; } } - public int RequestId { get { return requestId; } } + public bool DaemonIsRunning => daemonIsRunning; + public bool DaemonIsConnected => daemonIsConnected; + public int RequestId => requestId; protected Process daemonProcess; protected ManualResetEvent daemonLoopSignal = new ManualResetEvent(false); @@ -232,7 +234,7 @@ namespace OpenMetaverse.Voice if (daemonIsConnected) { StringBuilder sb = new StringBuilder(); - sb.Append(String.Format(""); @@ -267,10 +269,9 @@ namespace OpenMetaverse.Voice public static string MakeXML(string name, string text) { - if (string.IsNullOrEmpty(text)) - return string.Format("<{0} />", name); - else - return string.Format("<{0}>{1}", name, text); + return string.IsNullOrEmpty(text) + ? string.Format("<{0} />", name) + : string.Format("<{0}>{1}", name, text); } private void daemonPipe_OnReceiveLine(string line) @@ -299,26 +300,23 @@ namespace OpenMetaverse.Voice // These first responses carry useful information beyond simple status, // so they each have a specific Event. case "Connector.Create.1": - if (OnConnectorCreateResponse != null) - { - OnConnectorCreateResponse( - rsp.InputXml.Request, - new VoiceConnectorEventArgs( - int.Parse(rsp.ReturnCode), - int.Parse(rsp.Results.StatusCode), - rsp.Results.StatusString, - rsp.Results.VersionID, - rsp.Results.ConnectorHandle)); - } + OnConnectorCreateResponse?.Invoke( + rsp.InputXml.Request, + new VoiceConnectorEventArgs( + rsp.ReturnCode, + rsp.Results.StatusCode, + rsp.Results.StatusString, + rsp.Results.VersionID, + rsp.Results.ConnectorHandle)); break; case "Aux.GetCaptureDevices.1": - inputDevices = new List(); + CaptureDevices = new List(); if (rsp.Results.CaptureDevices.Count == 0 || rsp.Results.CurrentCaptureDevice == null) break; foreach (CaptureDevice device in rsp.Results.CaptureDevices) - inputDevices.Add(device.Device); + CaptureDevices.Add(device.Device); currentCaptureDevice = rsp.Results.CurrentCaptureDevice.Device; if (OnAuxGetCaptureDevicesResponse != null && rsp.Results.CaptureDevices.Count > 0) @@ -327,21 +325,21 @@ namespace OpenMetaverse.Voice rsp.InputXml.Request, new VoiceDevicesEventArgs( ResponseType.GetCaptureDevices, - int.Parse(rsp.ReturnCode), - int.Parse(rsp.Results.StatusCode), + rsp.ReturnCode, + rsp.Results.StatusCode, rsp.Results.StatusString, rsp.Results.CurrentCaptureDevice.Device, - inputDevices)); + CaptureDevices)); } break; case "Aux.GetRenderDevices.1": - outputDevices = new List(); + PlaybackDevices = new List(); if (rsp.Results.RenderDevices.Count == 0 || rsp.Results.CurrentRenderDevice == null) break; foreach (RenderDevice device in rsp.Results.RenderDevices) - outputDevices.Add(device.Device); + PlaybackDevices.Add(device.Device); currentPlaybackDevice = rsp.Results.CurrentRenderDevice.Device; @@ -352,11 +350,11 @@ namespace OpenMetaverse.Voice rsp.InputXml.Request, new VoiceDevicesEventArgs( ResponseType.GetCaptureDevices, - int.Parse(rsp.ReturnCode), - int.Parse(rsp.Results.StatusCode), + rsp.ReturnCode, + rsp.Results.StatusCode, rsp.Results.StatusString, rsp.Results.CurrentRenderDevice.Device, - outputDevices)); + PlaybackDevices)); } break; @@ -365,8 +363,8 @@ namespace OpenMetaverse.Voice { OnAccountLoginResponse(rsp.InputXml.Request, new VoiceAccountEventArgs( - int.Parse(rsp.ReturnCode), - int.Parse(rsp.Results.StatusCode), + rsp.ReturnCode, + rsp.Results.StatusCode, rsp.Results.StatusString, rsp.Results.AccountHandle)); } @@ -378,8 +376,8 @@ namespace OpenMetaverse.Voice OnSessionCreateResponse( rsp.InputXml.Request, new VoiceSessionEventArgs( - int.Parse(rsp.ReturnCode), - int.Parse(rsp.Results.StatusCode), + rsp.ReturnCode, + rsp.Results.StatusCode, rsp.Results.StatusString, rsp.Results.SessionHandle)); } @@ -456,8 +454,8 @@ namespace OpenMetaverse.Voice OnVoiceResponse(rsp.InputXml.Request, new VoiceResponseEventArgs( genericResponse, - int.Parse(rsp.ReturnCode), - int.Parse(rsp.Results.StatusCode), + rsp.ReturnCode, + rsp.Results.StatusCode, rsp.Results.StatusString)); } } @@ -478,15 +476,12 @@ namespace OpenMetaverse.Voice { case "LoginStateChangeEvent": case "AccountLoginStateChangeEvent": - if (OnAccountLoginStateChangeEvent != null) - { - OnAccountLoginStateChangeEvent(this, - new AccountLoginStateChangeEventArgs( - evt.AccountHandle, - int.Parse(evt.StatusCode), - evt.StatusString, - (LoginState)int.Parse(evt.State))); - } + OnAccountLoginStateChangeEvent?.Invoke(this, + new AccountLoginStateChangeEventArgs( + evt.AccountHandle, + int.Parse(evt.StatusCode), + evt.StatusString, + (LoginState)int.Parse(evt.State))); break; case "SessionNewEvent": diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceManager.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceManager.cs new file mode 100644 index 0000000..0e11b2d --- /dev/null +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceManager.cs @@ -0,0 +1,815 @@ +/* + * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. + * 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.Collections.Generic; +using System.Net.Sockets; +using System.Text; +using System.IO; +using System.Xml; +using OpenMetaverse.StructuredData; +using OpenMetaverse; +using OpenMetaverse.Http; +using OpenMetaverse.Interfaces; +using OpenMetaverse.Messages.Linden; + +namespace LibreMetaverse.Voice +{ + public enum VoiceStatus + { + StatusLoginRetry, + StatusLoggedIn, + StatusJoining, + StatusJoined, + StatusLeftChannel, + BeginErrorStatus, + ErrorChannelFull, + ErrorChannelLocked, + ErrorNotAvailable, + ErrorUnknown + } + + public enum VoiceServiceType + { + /// Unknown voice service level + Unknown, + /// Spatialized local chat + TypeA, + /// Remote multi-party chat + TypeB, + /// One-to-one and small group chat + TypeC + } + + public partial class VoiceManager + { + public const int VOICE_MAJOR_VERSION = 1; + public const string DAEMON_ARGS = " -p tcp -h -c -ll "; + public const int DAEMON_LOG_LEVEL = 1; + public const int DAEMON_PORT = 44124; + public const string VOICE_RELEASE_SERVER = "bhr.vivox.com"; + public const string VOICE_DEBUG_SERVER = "bhd.vivox.com"; + public const string REQUEST_TERMINATOR = "\n\n\n"; + + public delegate void LoginStateChangeCallback(int cookie, string accountHandle, int statusCode, string statusString, int state); + public delegate void NewSessionCallback(int cookie, string accountHandle, string eventSessionHandle, int state, string nameString, string uriString); + public delegate void SessionStateChangeCallback(int cookie, string uriString, int statusCode, string statusString, string eventSessionHandle, int state, bool isChannel, string nameString); + public delegate void ParticipantStateChangeCallback(int cookie, string uriString, int statusCode, string statusString, int state, string nameString, string displayNameString, int participantType); + public delegate void ParticipantPropertiesCallback(int cookie, string uriString, int statusCode, string statusString, bool isLocallyMuted, bool isModeratorMuted, bool isSpeaking, int volume, float energy); + public delegate void AuxAudioPropertiesCallback(int cookie, float energy); + public delegate void BasicActionCallback(int cookie, int statusCode, string statusString); + public delegate void ConnectorCreatedCallback(int cookie, int statusCode, string statusString, string connectorHandle); + public delegate void LoginCallback(int cookie, int statusCode, string statusString, string accountHandle); + public delegate void SessionCreatedCallback(int cookie, int statusCode, string statusString, string sessionHandle); + public delegate void DevicesCallback(int cookie, int statusCode, string statusString, string currentDevice); + public delegate void ProvisionAccountCallback(string username, string password); + public delegate void ParcelVoiceInfoCallback(string regionName, int localID, string channelURI); + + public event LoginStateChangeCallback OnLoginStateChange; + public event NewSessionCallback OnNewSession; + public event SessionStateChangeCallback OnSessionStateChange; + public event ParticipantStateChangeCallback OnParticipantStateChange; + public event ParticipantPropertiesCallback OnParticipantProperties; + public event AuxAudioPropertiesCallback OnAuxAudioProperties; + public event ConnectorCreatedCallback OnConnectorCreated; + public event LoginCallback OnLogin; + public event SessionCreatedCallback OnSessionCreated; + public event BasicActionCallback OnSessionConnected; + public event BasicActionCallback OnAccountLogout; + public event BasicActionCallback OnConnectorInitiateShutdown; + public event BasicActionCallback OnAccountChannelGetList; + public event BasicActionCallback OnSessionTerminated; + public event DevicesCallback OnCaptureDevices; + public event DevicesCallback OnRenderDevices; + public event ProvisionAccountCallback OnProvisionAccount; + public event ParcelVoiceInfoCallback OnParcelVoiceInfo; + + public GridClient Client; + public string VoiceServer = VOICE_RELEASE_SERVER; + public bool Enabled; + + protected Voice.TCPPipe _DaemonPipe; + protected VoiceStatus _Status; + protected int _CommandCookie = 0; + protected string _TuningSoundFile = string.Empty; + protected Dictionary _ChannelMap = new Dictionary(); + protected List _CaptureDevices = new List(); + protected List _RenderDevices = new List(); + + #region Response Processing Variables + + private bool isEvent; + private bool isChannel; + private bool isLocallyMuted; + private bool isModeratorMuted; + private bool isSpeaking; + private int cookie; + //private int returnCode; + private int statusCode; + private int volume; + private int state; + private int participantType; + private float energy; + private string statusString = string.Empty; + //private string uuidString = string.Empty; + private string actionString = string.Empty; + private string connectorHandle = string.Empty; + private string accountHandle = string.Empty; + private string sessionHandle = string.Empty; + private string eventSessionHandle = string.Empty; + private string eventTypeString = string.Empty; + private string uriString = string.Empty; + private string nameString = string.Empty; + //private string audioMediaString = string.Empty; + private string displayNameString = string.Empty; + + #endregion Response Processing Variables + + public VoiceManager(GridClient client) + { + Client = client; + Client.Network.RegisterEventCallback("RequiredVoiceVersion", RequiredVoiceVersionEventHandler); + + // Register callback handlers for the blocking functions + RegisterCallbacks(); + + Enabled = true; + } + + public bool IsDaemonRunning() + { + throw new NotImplementedException(); + } + + public bool StartDaemon() + { + throw new NotImplementedException(); + } + + public void StopDaemon() + { + throw new NotImplementedException(); + } + + public bool ConnectToDaemon() + { + if (!Enabled) return false; + + return ConnectToDaemon("127.0.0.1", DAEMON_PORT); + } + + public bool ConnectToDaemon(string address, int port) + { + if (!Enabled) return false; + + _DaemonPipe = new Voice.TCPPipe(); + _DaemonPipe.OnDisconnected += _DaemonPipe_OnDisconnected; + _DaemonPipe.OnReceiveLine += _DaemonPipe_OnReceiveLine; + + var se = _DaemonPipe.Connect(address, port); + + if (se == null) + { + return true; + } + Console.WriteLine("Connection failed: " + se.Message); + return false; + } + + public Dictionary GetChannelMap() + { + return new Dictionary(_ChannelMap); + } + + public List CurrentCaptureDevices() + { + return new List(_CaptureDevices); + } + + public List CurrentRenderDevices() + { + return new List(_RenderDevices); + } + + public string VoiceAccountFromUUID(UUID id) + { + var result = "x" + Convert.ToBase64String(id.GetBytes()); + return result.Replace('+', '-').Replace('/', '_'); + } + + public UUID UUIDFromVoiceAccount(string accountName) + { + if (accountName.Length == 25 && accountName[0] == 'x' && accountName[23] == '=' && accountName[24] == '=') + { + accountName = accountName.Replace('/', '_').Replace('+', '-'); + var idBytes = Convert.FromBase64String(accountName); + + return idBytes.Length == 16 ? new UUID(idBytes, 0) : UUID.Zero; + } + return UUID.Zero; + } + + public string SIPURIFromVoiceAccount(string account) + { + return $"sip:{account}@{VoiceServer}"; + } + + public int RequestCaptureDevices() + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + Logger.Log("VoiceManager.RequestCaptureDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + + public int RequestRenderDevices() + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + Logger.Log("VoiceManager.RequestRenderDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + + public int RequestCreateConnector() + { + return RequestCreateConnector(VoiceServer); + } + + public int RequestCreateConnector(string voiceServer) + { + if (_DaemonPipe.Connected) + { + VoiceServer = voiceServer; + + var accountServer = $"https://www.{VoiceServer}/api2/"; + var logPath = "."; + + var request = new StringBuilder(); + request.Append($""); + request.Append("V2 SDK"); + request.Append($"{accountServer}"); + request.Append(""); + request.Append("false"); + request.Append($"{logPath}"); + request.Append("vivox-gateway"); + request.Append(".log"); + request.Append("0"); + request.Append(""); + request.Append(""); + request.Append(REQUEST_TERMINATOR); + + _DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString())); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.CreateConnector() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + private bool RequestVoiceInternal(string me, CapsClient.CompleteCallback callback, string capsName) + { + if (Enabled && Client.Network.Connected) + { + if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.Caps != null) + { + var url = Client.Network.CurrentSim.Caps.CapabilityURI(capsName); + + if (url != null) + { + var request = new CapsClient(url); + var body = new OSDMap(); + request.OnComplete += callback; + request.PostRequestAsync(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + + return true; + } + Logger.Log("VoiceManager." + me + "(): " + capsName + " capability is missing", + Helpers.LogLevel.Info, Client); + return false; + } + } + + Logger.Log("VoiceManager.RequestVoiceInternal(): Voice system is currently disabled", + Helpers.LogLevel.Info, Client); + return false; + + } + + public bool RequestProvisionAccount() + { + return RequestVoiceInternal("RequestProvisionAccount", ProvisionCapsResponse, "ProvisionVoiceAccountRequest"); + } + + public bool RequestParcelVoiceInfo() + { + return RequestVoiceInternal("RequestParcelVoiceInfo", ParcelVoiceInfoResponse, "ParcelVoiceInfoRequest"); + } + + public int RequestLogin(string accountName, string password, string connectorHandle) + { + if (_DaemonPipe.Connected) + { + var request = new StringBuilder(); + request.Append($""); + request.Append($"{connectorHandle}"); + request.Append($"{accountName}"); + request.Append($"{password}"); + request.Append("VerifyAnswer"); + request.Append(""); + request.Append("10"); + request.Append("false"); + request.Append(""); + request.Append(REQUEST_TERMINATOR); + + _DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString())); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.Login() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestSetRenderDevice(string deviceName) + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{deviceName}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestSetRenderDevice() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestStartTuningMode(int duration) + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{duration}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestStartTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestStopTuningMode() + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestStopTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return _CommandCookie - 1; + } + } + + public int RequestSetSpeakerVolume(int volume_) + { + if (volume_ < 0 || volume_ > 100) + throw new ArgumentException("volume must be between 0 and 100", nameof(volume_)); + + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{volume_}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestSetSpeakerVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestSetCaptureVolume(int volume_) + { + if (volume_ < 0 || volume_ > 100) + throw new ArgumentException("volume must be between 0 and 100", nameof(volume_)); + + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{volume_}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestSetCaptureVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + /// + /// Does not appear to be working + /// + /// + /// + public int RequestRenderAudioStart(string fileName, bool loop) + { + if (_DaemonPipe.Connected) + { + _TuningSoundFile = fileName; + + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{_TuningSoundFile}{(loop ? "1" : "0")}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestRenderAudioStart() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestRenderAudioStop() + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{_TuningSoundFile}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestRenderAudioStop() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + #region Callbacks + + private void RequiredVoiceVersionEventHandler(string capsKey, IMessage message, Simulator simulator) + { + var msg = (RequiredVoiceVersionMessage)message; + + if (VOICE_MAJOR_VERSION != msg.MajorVersion) + { + Logger.Log( + $"Voice version mismatch! Got {msg.MajorVersion}, expecting {VOICE_MAJOR_VERSION}. Disabling the voice manager", Helpers.LogLevel.Error, Client); + Enabled = false; + } + else + { + Logger.DebugLog("Voice version " + msg.MajorVersion + " verified", Client); + } + } + + private void ProvisionCapsResponse(CapsClient client, OSD response, Exception error) + { + if (!(response is OSDMap respMap)) return; + + if (OnProvisionAccount == null) return; + try { OnProvisionAccount(respMap["username"].AsString(), respMap["password"].AsString()); } + catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } + } + + private void ParcelVoiceInfoResponse(CapsClient client, OSD response, Exception error) + { + if (!(response is OSDMap respMap)) return; + + var regionName = respMap["region_name"].AsString(); + var localID = respMap["parcel_local_id"].AsInteger(); + + string channelURI = null; + if (respMap["voice_credentials"] is OSDMap) + { + var creds = (OSDMap)respMap["voice_credentials"]; + channelURI = creds["channel_uri"].AsString(); + } + + OnParcelVoiceInfo?.Invoke(regionName, localID, channelURI); + } + + private static void _DaemonPipe_OnDisconnected(SocketException se) + { + if (se != null) Console.WriteLine("Disconnected! " + se.Message); + else Console.WriteLine("Disconnected!"); + } + + private void _DaemonPipe_OnReceiveLine(string line) + { + var reader = new XmlTextReader(new StringReader(line)); + + while (reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + { + if (reader.Depth == 0) + { + isEvent = (reader.Name == "Event"); + + if (isEvent || reader.Name == "Response") + { + for (var i = 0; i < reader.AttributeCount; i++) + { + reader.MoveToAttribute(i); + + if (reader.Name == "action") + actionString = reader.Value; + else if (reader.Name == "type") + eventTypeString = reader.Value; + } + } + } + else + { + switch (reader.Name) + { + case "InputXml": + cookie = -1; + + // Parse through here to get the cookie value + reader.Read(); + if (reader.Name == "Request") + { + for (var i = 0; i < reader.AttributeCount; i++) + { + reader.MoveToAttribute(i); + + if (reader.Name != "requestId") continue; + int.TryParse(reader.Value, out cookie); + break; + } + } + + if (cookie == -1) + { + Logger.Log( + "VoiceManager._DaemonPipe_OnReceiveLine(): Failed to parse InputXml for the cookie", + Helpers.LogLevel.Warning, Client); + } + break; + case "CaptureDevices": + _CaptureDevices.Clear(); + break; + case "RenderDevices": + _RenderDevices.Clear(); + break; +// case "ReturnCode": +// returnCode = reader.ReadElementContentAsInt(); +// break; + case "StatusCode": + statusCode = reader.ReadElementContentAsInt(); + break; + case "StatusString": + statusString = reader.ReadElementContentAsString(); + break; + case "State": + state = reader.ReadElementContentAsInt(); + break; + case "ConnectorHandle": + connectorHandle = reader.ReadElementContentAsString(); + break; + case "AccountHandle": + accountHandle = reader.ReadElementContentAsString(); + break; + case "SessionHandle": + sessionHandle = reader.ReadElementContentAsString(); + break; + case "URI": + uriString = reader.ReadElementContentAsString(); + break; + case "IsChannel": + isChannel = reader.ReadElementContentAsBoolean(); + break; + case "Name": + nameString = reader.ReadElementContentAsString(); + break; +// case "AudioMedia": +// audioMediaString = reader.ReadElementContentAsString(); +// break; + case "ChannelName": + nameString = reader.ReadElementContentAsString(); + break; + case "ParticipantURI": + uriString = reader.ReadElementContentAsString(); + break; + case "DisplayName": + displayNameString = reader.ReadElementContentAsString(); + break; + case "AccountName": + nameString = reader.ReadElementContentAsString(); + break; + case "ParticipantType": + participantType = reader.ReadElementContentAsInt(); + break; + case "IsLocallyMuted": + isLocallyMuted = reader.ReadElementContentAsBoolean(); + break; + case "IsModeratorMuted": + isModeratorMuted = reader.ReadElementContentAsBoolean(); + break; + case "IsSpeaking": + isSpeaking = reader.ReadElementContentAsBoolean(); + break; + case "Volume": + volume = reader.ReadElementContentAsInt(); + break; + case "Energy": + energy = reader.ReadElementContentAsFloat(); + break; + case "MicEnergy": + energy = reader.ReadElementContentAsFloat(); + break; + case "ChannelURI": + uriString = reader.ReadElementContentAsString(); + break; + case "ChannelListResult": + _ChannelMap[nameString] = uriString; + break; + case "CaptureDevice": + reader.Read(); + _CaptureDevices.Add(reader.ReadElementContentAsString()); + break; + case "CurrentCaptureDevice": + reader.Read(); + nameString = reader.ReadElementContentAsString(); + break; + case "RenderDevice": + reader.Read(); + _RenderDevices.Add(reader.ReadElementContentAsString()); + break; + case "CurrentRenderDevice": + reader.Read(); + nameString = reader.ReadElementContentAsString(); + break; + } + } + + break; + } + case XmlNodeType.EndElement: + if (reader.Depth == 0) + ProcessEvent(); + break; + case XmlNodeType.None: + break; + case XmlNodeType.Attribute: + break; + case XmlNodeType.Text: + break; + case XmlNodeType.CDATA: + break; + case XmlNodeType.EntityReference: + break; + case XmlNodeType.Entity: + break; + case XmlNodeType.ProcessingInstruction: + break; + case XmlNodeType.Comment: + break; + case XmlNodeType.Document: + break; + case XmlNodeType.DocumentType: + break; + case XmlNodeType.DocumentFragment: + break; + case XmlNodeType.Notation: + break; + case XmlNodeType.Whitespace: + break; + case XmlNodeType.SignificantWhitespace: + break; + case XmlNodeType.EndEntity: + break; + case XmlNodeType.XmlDeclaration: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + if (isEvent) + { + } + + //Client.DebugLog("VOICE: " + line); + } + + private void ProcessEvent() + { + if (isEvent) + { + switch (eventTypeString) + { + case "LoginStateChangeEvent": + OnLoginStateChange?.Invoke(cookie, accountHandle, statusCode, statusString, state); + break; + case "SessionNewEvent": + OnNewSession?.Invoke(cookie, accountHandle, eventSessionHandle, state, nameString, uriString); + break; + case "SessionStateChangeEvent": + OnSessionStateChange?.Invoke(cookie, uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString); + break; + case "ParticipantStateChangeEvent": + OnParticipantStateChange?.Invoke(cookie, uriString, statusCode, statusString, state, nameString, displayNameString, participantType); + break; + case "ParticipantPropertiesEvent": + OnParticipantProperties?.Invoke(cookie, uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy); + break; + case "AuxAudioPropertiesEvent": + OnAuxAudioProperties?.Invoke(cookie, energy); + break; + } + } + else + { + switch (actionString) + { + case "Connector.Create.1": + OnConnectorCreated?.Invoke(cookie, statusCode, statusString, connectorHandle); + break; + case "Account.Login.1": + OnLogin?.Invoke(cookie, statusCode, statusString, accountHandle); + break; + case "Session.Create.1": + OnSessionCreated?.Invoke(cookie, statusCode, statusString, sessionHandle); + break; + case "Session.Connect.1": + OnSessionConnected?.Invoke(cookie, statusCode, statusString); + break; + case "Session.Terminate.1": + OnSessionTerminated?.Invoke(cookie, statusCode, statusString); + break; + case "Account.Logout.1": + OnAccountLogout?.Invoke(cookie, statusCode, statusString); + break; + case "Connector.InitiateShutdown.1": + OnConnectorInitiateShutdown?.Invoke(cookie, statusCode, statusString); + break; + case "Account.ChannelGetList.1": + OnAccountChannelGetList?.Invoke(cookie, statusCode, statusString); + break; + case "Aux.GetCaptureDevices.1": + OnCaptureDevices?.Invoke(cookie, statusCode, statusString, nameString); + break; + case "Aux.GetRenderDevices.1": + OnRenderDevices?.Invoke(cookie, statusCode, statusString, nameString); + break; + } + } + } + + #endregion Callbacks + } +} diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceManager.cs.meta b/Assets/Plugins/LibreMetaverse/Voice/VoiceManager.cs.meta new file mode 100644 index 0000000..cb74384 --- /dev/null +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 41f5cff506a7762458aa0fa06a3a635b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceManagerBlocking.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceManagerBlocking.cs new file mode 100644 index 0000000..e066f60 --- /dev/null +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceManagerBlocking.cs @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. + * 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.Collections.Generic; +using System.Threading; + +namespace LibreMetaverse.Voice +{ + public partial class VoiceManager + { + /// Amount of time to wait for the voice daemon to respond. + /// The value needs to stay relatively high because some of the calls + /// require the voice daemon to make remote queries before replying + public int BlockingTimeout = 30 * 1000; + + protected Dictionary Events = new Dictionary(); + + public List CaptureDevices() + { + var evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestCaptureDevices() == -1) + { + Events.Remove(_CommandCookie); + return new List(); + } + + return evt.WaitOne(BlockingTimeout, false) ? CurrentCaptureDevices() : new List(); + } + + public List RenderDevices() + { + var evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestRenderDevices() == -1) + { + Events.Remove(_CommandCookie); + return new List(); + } + + return evt.WaitOne(BlockingTimeout, false) ? CurrentRenderDevices() : new List(); + } + + public string CreateConnector(out int status) + { + status = 0; + + var evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestCreateConnector() == -1) + { + Events.Remove(_CommandCookie); + return string.Empty; + } + + var success = evt.WaitOne(BlockingTimeout, false); + status = statusCode; + + return success && statusCode == 0 ? connectorHandle : string.Empty; + } + + public string Login(string accountName, string password, string connectorHandle, out int status) + { + status = 0; + + var evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestLogin(accountName, password, connectorHandle) == -1) + { + Events.Remove(_CommandCookie); + return string.Empty; + } + + var success = evt.WaitOne(BlockingTimeout, false); + status = statusCode; + + return success && statusCode == 0 ? accountHandle : string.Empty; + } + + protected void RegisterCallbacks() + { + OnCaptureDevices += VoiceManager_OnCaptureDevices; + OnRenderDevices += VoiceManager_OnRenderDevices; + OnConnectorCreated += VoiceManager_OnConnectorCreated; + OnLogin += VoiceManager_OnLogin; + } + + #region Callbacks + + private void VoiceManager_OnCaptureDevices(int cookie, int statusCode, string statusString, string currentDevice) + { + if (Events.ContainsKey(cookie)) + Events[cookie].Set(); + } + + private void VoiceManager_OnRenderDevices(int cookie, int statusCode, string statusString, string currentDevice) + { + if (Events.ContainsKey(cookie)) + Events[cookie].Set(); + } + + private void VoiceManager_OnConnectorCreated(int cookie, int statusCode, string statusString, string connectorHandle) + { + if (Events.ContainsKey(cookie)) + Events[cookie].Set(); + } + + private void VoiceManager_OnLogin(int cookie, int statusCode, string statusString, string accountHandle) + { + if (Events.ContainsKey(cookie)) + Events[cookie].Set(); + } + + #endregion Callbacks + } +} \ No newline at end of file diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceManagerBlocking.cs.meta b/Assets/Plugins/LibreMetaverse/Voice/VoiceManagerBlocking.cs.meta new file mode 100644 index 0000000..fe29ad2 --- /dev/null +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceManagerBlocking.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9b80357650347d34e93d925b1fb0941d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceParticipant.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceParticipant.cs index 135d9a2..6a8a56e 100644 --- a/Assets/Plugins/LibreMetaverse/Voice/VoiceParticipant.cs +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceParticipant.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -25,34 +26,29 @@ */ using System; -using System.Collections.Generic; -using System.Linq; using System.Text; using System.Text.RegularExpressions; +using OpenMetaverse; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public class VoiceParticipant { - private string Sip; private string AvatarName { get; set; } - private UUID id; private bool muted; private int volume; - private VoiceSession session; - private float energy; + private readonly VoiceSession session; - public float Energy { get { return energy; } } - private bool speaking; - public bool IsSpeaking { get { return speaking; } } - public string URI { get { return Sip; } } - public UUID ID { get { return id; } } + public float Energy { get; private set; } + public bool IsSpeaking { get; private set; } + public string URI { get; } + public UUID ID { get; } public VoiceParticipant(string puri, VoiceSession s) { - id = IDFromName(puri); - Sip = puri; + ID = IDFromName(puri); + URI = puri; session = s; } @@ -92,7 +88,7 @@ namespace OpenMetaverse.Voice private static string Encode64(string str) { - byte[] encbuff = System.Text.Encoding.UTF8.GetBytes(str); + byte[] encbuff = Encoding.UTF8.GetBytes(str); return Convert.ToBase64String(encbuff); } private static byte[] Decode64(string str) @@ -116,43 +112,43 @@ namespace OpenMetaverse.Voice public string Name { - get { return AvatarName; } - set { AvatarName = value; } + get => AvatarName; + set => AvatarName = value; } public bool IsMuted { - get { return muted; } + get => muted; set { muted = value; StringBuilder sb = new StringBuilder(); - sb.Append(OpenMetaverse.Voice.VoiceGateway.MakeXML("SessionHandle", session.Handle)); - sb.Append(OpenMetaverse.Voice.VoiceGateway.MakeXML("ParticipantURI", Sip)); - sb.Append(OpenMetaverse.Voice.VoiceGateway.MakeXML("Mute", muted ? "1" : "0")); + sb.Append(VoiceGateway.MakeXML("SessionHandle", session.Handle)); + sb.Append(VoiceGateway.MakeXML("ParticipantURI", URI)); + sb.Append(VoiceGateway.MakeXML("Mute", muted ? "1" : "0")); session.Connector.Request("Session.SetParticipantMuteForMe.1", sb.ToString()); } } public int Volume { - get { return volume; } + get => volume; set { volume = value; StringBuilder sb = new StringBuilder(); - sb.Append(OpenMetaverse.Voice.VoiceGateway.MakeXML("SessionHandle", session.Handle)); - sb.Append(OpenMetaverse.Voice.VoiceGateway.MakeXML("ParticipantURI", Sip)); - sb.Append(OpenMetaverse.Voice.VoiceGateway.MakeXML("Volume", volume.ToString())); + sb.Append(VoiceGateway.MakeXML("SessionHandle", session.Handle)); + sb.Append(VoiceGateway.MakeXML("ParticipantURI", URI)); + sb.Append(VoiceGateway.MakeXML("Volume", volume.ToString())); session.Connector.Request("Session.SetParticipantVolumeForMe.1", sb.ToString()); } } internal void SetProperties(bool speak, bool mute, float en) { - speaking = speak; + IsSpeaking = speak; muted = mute; - energy = en; + Energy = en; } } } diff --git a/Assets/Plugins/LibreMetaverse/Voice/VoiceSession.cs b/Assets/Plugins/LibreMetaverse/Voice/VoiceSession.cs index aadc96e..7239275 100644 --- a/Assets/Plugins/LibreMetaverse/Voice/VoiceSession.cs +++ b/Assets/Plugins/LibreMetaverse/Voice/VoiceSession.cs @@ -1,5 +1,6 @@ /* * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -24,27 +25,23 @@ * POSSIBILITY OF SUCH DAMAGE. */ -using System; using System.Collections.Generic; +using System.Globalization; using System.Text; -using OpenMetaverse; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { /// /// Represents a single Voice Session to the Vivox service. /// public class VoiceSession { - private string m_Handle; private static Dictionary knownParticipants; public string RegionName; - private bool m_spatial; - public bool IsSpatial { get { return m_spatial; } } - private VoiceGateway connector; + public bool IsSpatial { get; } - public VoiceGateway Connector { get { return connector; } } - public string Handle { get { return m_Handle; } } + public VoiceGateway Connector { get; } + public string Handle { get; } public event System.EventHandler OnParticipantAdded; public event System.EventHandler OnParticipantUpdate; @@ -52,10 +49,10 @@ namespace OpenMetaverse.Voice public VoiceSession(VoiceGateway conn, string handle) { - m_Handle = handle; - connector = conn; + Handle = handle; + Connector = conn; - m_spatial = true; + IsSpatial = true; knownParticipants = new Dictionary(); } @@ -150,7 +147,7 @@ namespace OpenMetaverse.Voice public void Set3DPosition(VoicePosition SpeakerPosition, VoicePosition ListenerPosition) { - connector.SessionSet3DPosition(m_Handle, SpeakerPosition, ListenerPosition); + Connector.SessionSet3DPosition(Handle, SpeakerPosition, ListenerPosition); } } @@ -263,56 +260,56 @@ namespace OpenMetaverse.Voice sb.Append(VoiceGateway.MakeXML("SessionHandle", SessionHandle)); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.Position.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.Position.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.Position.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.Position.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.Position.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.Position.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.Velocity.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.Velocity.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.Velocity.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.Velocity.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.Velocity.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.Velocity.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.AtOrientation.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.AtOrientation.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.AtOrientation.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.AtOrientation.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.AtOrientation.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.AtOrientation.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.UpOrientation.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.UpOrientation.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.UpOrientation.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.UpOrientation.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.UpOrientation.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.UpOrientation.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.LeftOrientation.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.LeftOrientation.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.LeftOrientation.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", SpeakerPosition.LeftOrientation.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", SpeakerPosition.LeftOrientation.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", SpeakerPosition.LeftOrientation.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.Position.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.Position.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.Position.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.Position.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.Position.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.Position.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.Velocity.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.Velocity.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.Velocity.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.Velocity.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.Velocity.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.Velocity.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.AtOrientation.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.AtOrientation.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.AtOrientation.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.AtOrientation.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.AtOrientation.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.AtOrientation.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.UpOrientation.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.UpOrientation.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.UpOrientation.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.UpOrientation.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.UpOrientation.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.UpOrientation.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); - sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.LeftOrientation.X.ToString())); - sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.LeftOrientation.Y.ToString())); - sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.LeftOrientation.Z.ToString())); + sb.Append(VoiceGateway.MakeXML("X", ListenerPosition.LeftOrientation.X.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Y", ListenerPosition.LeftOrientation.Y.ToString(CultureInfo.InvariantCulture))); + sb.Append(VoiceGateway.MakeXML("Z", ListenerPosition.LeftOrientation.Z.ToString(CultureInfo.InvariantCulture))); sb.Append(""); sb.Append(""); return Request("Session.Set3DPosition.1", sb.ToString()); diff --git a/Assets/Plugins/LibreMetaverse/_Packets_.cs b/Assets/Plugins/LibreMetaverse/_Packets_.cs index 6a82ae8..096c5f3 100644 --- a/Assets/Plugins/LibreMetaverse/_Packets_.cs +++ b/Assets/Plugins/LibreMetaverse/_Packets_.cs @@ -25,8 +25,6 @@ */ using System; -using System.Text; -using OpenMetaverse; namespace OpenMetaverse { @@ -76692,7 +76690,7 @@ namespace OpenMetaverse.Packets if (TextureEntry != null) { length += Math.Min(TextureEntry.Length, 65535); } if (TextureAnim != null) { length += (byte)TextureAnim.Length; } if (NameValue != null) { length += Math.Min(NameValue.Length, 65535); } - if (Data != null) { length += Math.Min(Data.Length, 65535); ; } + if (Data != null) { length += Math.Min(Data.Length, 65535); } if (Text != null) { length += (byte)Text.Length; } if (MediaURL != null) { length += (byte)MediaURL.Length; } if (PSBlock != null) { length += (byte)PSBlock.Length; } diff --git a/Assets/Plugins/OggVorbisEncoder.dll b/Assets/Plugins/OggVorbisEncoder.dll new file mode 100644 index 0000000000000000000000000000000000000000..d019de213de3aef32914e1513051e741a21be815 GIT binary patch literal 895488 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

I)GD!#RvL+t6gw<`4aCA(Sw8<2XgeL% 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

9AI5Nhj8^Ybc>E(JQG^}KgkTF=S*BZ z=i=(Q(7Eqf%et;t30Kc1Ts>QH_3XgaQ^D1<8&}UhTs;S!`<^kU)t;YgarI2X)iVuO z&rDoBb8z)6z}2%DSI;u%zUP=8wLK@`>gkNDr#r5mGja8ti>v1XTs;@#>KWnO_iSQa z*J~@To*lS)D!6)flum@vG-Z=e}n$>*~qj>bV0~&sUay|Gdivn%>4&RlAg-QaxOy(f)pHfDp6i_Zo(k(aKf7`D?8DXb z8Lpo2jOcjO(-c=vOI$teaP@R_?tA95uAW7>dY0nqS%Ir(4X&Q`xOz6?>UkMg&vxg& z=OF9qsqYybuX>Ke)zb!7PkUTF9dY%Xf~)5YTs^&=`<~^jt7kQ?p0&7oO1OG9;p*9n zt7iwUo(is>51jj+QD@dZpGV{B8HcN90Y0VBXCAJe2b}w!X5XysX@#rj zcw9Xv;_5jWSI_CVdd|kxb3U$~0nUBTI@Wc)p2gL(8CTCXTs=E+^}K_tXAiEP{kVD# zI`=)}&#FB?6LIxS#nm$dSI=x*J@aw(EW*{Z6j#qG=f3CoUbQ_Z;_5jWSI_CVdd|kx zb3U$~i*WS}!PPU$x$oJ`x~|tYTs=E+^}K_tXAiEP{kVEQ$JG-%J34;#9O>NmOl4g? zGjR3H#?><)SI;6`Jxg)*tiaW?23Jqfx$o(APVM#TiL0j%uAY9ldIsX^8HTIpa$G%E z;p(~0x$oJ@y3Wr#xO(>B>e-L0=W|>=vEI?~sizsPo>sVe+B^3>vsqWqd|W+?aP=(3 z)w2Ru&l+4k>v8pL#MQIKx$jB!sUA<{U0Pij4*oB12rde5iM&b6uY^If@H^q1&U3+^ zE(n8$;5*@c_>Rtj`GBy?dG7Ior_Kz6tt|(JL8|JxV9}L9(7n~g;i$fmuRS{m&S^C) ze79fZcO}E%f>xJ>$6ZqOsApuW(cz{b)Q{K#ngzuDvcqSd5u z)D_YF<3qwA-|EJ&O*-;9XNAG4R@1^qudMpqpwks$@NBDF!#()T*8gIw+rutDi0=QE zJ>HwG?g|Htj_$wg@-X;EtGQv;`OKi(H^boLR`-OTULCEsZxIB|S}zQwI|kwN=jrfBbG3bZY%z*yP92_OF~01gEuL5?=k2$mdM1`orOM6C>}k zs_M(ayC+54KW69uoYs$oeJ4l0_R8wxtqecCIr8q`3xf+=uL{3Dt?E%vs}`%nevvoX zV%PVVM?V%0be?PSXY&;;9}Cmbda&PoWUJqXcmLP+Lt3v1Kf0y*cyog&@Z8d!qF( z&I^MFTfY*1yf9k-hxzYXzaCC`pz3pj)Ma6?!~SQUui%&1^P#)_&&fV^o(ukJ*ZcO? zyTkg$(f0FtRgd5P=SKO+r(7Hc`&xexKJI*OaE(2FeVcvZg@x$xZ#6%r&8OjY_$=q2 zhxg$V%ui@@Fnk6-&wOP47vWC)4Cns|KgGM)@s6wyVi!K>9{;#$*0-v87<=j0RgbPm zr#4}1`lHU>^W)Sub+Ol+=Yqxd`aZQy(^#7o)%9HPi1VXjv+yU(&$C}1`*@{${9v#> zp9i#Q7dzuOk$1EGjc9X1Y}M-M@lUbymuZuTed;_Hd^$1=#<%Gh8~WSm@jKi4_%@wm zS3K^vr?*S&8s|}O7<7r<;QZDgbz~6yv`v@TO*QxL8k<>j|E{q)+&|;)-!*n0{>h2e z?N5$9i0^glUynV4cR#7ReoE{y=X2@l7JJ6Idb-7)=l*kT`)O^u#kS*(npeGh>@DZ- zHJva#4EnV^HTHhZ_NT^vwWfN!etns9J^rb&yPl|BpXc1K|M+*c>o+?0>)(B{c72p{ zzutOn?RtCXe*M+M*55pA{TJ(Mw|~I7-~PK#)vk|n?$=vCUAx}ixnF+xnH0Ba_#zb=YDwnl*`}kX&`|Y25wYskN zr&D8pbne&x<4Eq}6A3qkH&HbnKj_x<%{Am4_OCvvE z%|TEXtQiyCuKkI>>L1;&pM5Q>KJI+_)Am`R>ObJ|Vn_dncMyW^@0-r_iQzoR$~ z?N7HK&vD43$EyoINJabA?a}dvfqbuhCaiYJ)p4JF)~KGR=5+kod3}&`-Vj{j*eK}c z*f?10*2AE>ef6K8x$_eXme^;|>f?{&JpAS>(c?br&Qo2`ctF+bgEt06_xqP)ENIR7 zUB~S@-p)MUX0BhAF?;;+_F1;t`=))SsP3}JYt^-tKWqT zakS?XJtaHOM;~1m{EEl_TC#e-80&Q$M}08bJx)XLsp}4dZSMN~@B2mT4ctEr)T#cR z?s*)YuWE197Yh#dtnT-4ujBgEb3Ge^{_c6)D461&PmQ@QO@gO*e)Vvjdb~RBU(b3& z@E*sn{od@nj`LF=+{60ej}twg>e+7tkJpI(H~#W*^*oE7$FU%K{nQ1~>!&_g?fOG* zk8yila4*+g+gi5-fiynR~P);JkYwuZhwt}uOD_Cx^Dlc_tofqUC*2F%j(UTx zcjvQF@Qh>Qpu5|yNpPoo-ZTxixc8T4!D;UO;)vjJ_qsna*zX>%d2q8kzb%5#-Fg^& z!23}2{t^bqy7LhW#=DNX;Cc6asSnz@?HYogxQ<4_Lic{$IQXjTZxX!kzCJV!`ZzWV z2DFbJ)Z?${Bhy~}n^HLZ5jPri7J3q1Dx9;^? z7wmMb54O11c|*|6u~Bf7J70~14(>WO3BK#jL(`yx`?}IB_<`dQ!A{rhKM&}5MfEm! z&jWkkcB~I>;&pJ3J0FdLjjpe8(9Z3rNwC}9-ZaR#>(?x3>fT3>2wJ$~Ix;xj9ar<< z6#E%d5VQ!&?z|rr9Ew-{=-^6sJz56ecgN8x=;^+`wGNJP=dVq$&b@!N4X$(VU&jP< z+T+uU_+6ol^i+c;R~UQbPef4kRD)8N)l zc3OgF!2F(DgIKg=s457O<&p&;gKAd{qe(tVEEQo!r+F2J2Y#$wGD|fvcf;a4Y_#kK$jOkTn zr zZ-ML6=ix9o!<|3-yvFUb-F1n%&tvX=+C4w*=Lft_PvPskzWz6H&rkch;%=`GW*)Xa z#cf{~+{5EkKW|q5S?gXWvEWL#Kl}OH(be-43$E{4WnHk!d3`X!c|&lO-7mEB`Lu0U z{bztXE`3g{3(m9KWA^@1-7aRI)1vME?;p4NIlKB#^>cmo5!$=QtqbaSzxc@BKdZM_ z_gn3XI_v3daQm%ZXFX5!xm}-&rwxy`>&e&a=<82Ika5S|h|g_}zuZ20oEZD5V?WVx zRX=}5$JHp1jwkwDWIvbV^Ys^ej*r&s`5b3IM|bBb7VI4poj0vN=-y`= zf=*{wZ}*>bqvwx(U&Zt9Ii7!u-RnCR6x{nqU66LK>w2DV4Lpw;x#wZ^c>eqOSoQA7 z==?;F<3Gn2$5>GP{1@8! z9dE~LKb3X+t^U`6-tO~5BffuV%=dFmc%C$M*SUHgs*h8>j_r9J_Tckw^g3w>qSsra z;Fe_devNs&COlr#V7Pls^f9(>$zKR2#(@) z6nTvM*9AB7{oix$arOOAqu^fmxukJ$mOUS<#}U1c#dsh4-#(x3>!Z(U(eu8#UG)6$ zjNb2K!8-T4^WUG2bFY`W;0X8i$6mkgx-@V-8U@esdeZCm|Lgll--p$)e|udWw*Oza z^W~p+(Q!5e`u<2ikEp$0$nn|xs5?GAzP~;C-2A_No_l@Uf0y`w$$jo~QtZp0JKXH{ zub*SYg1_7I`=nq(@b%!v;M8DBaGFgIo6~K+VRME}Pn$DszG-uoO)s0XZO*ajZPUl* zT$_u6>A`nw2G|U=x!C3sn?W{%ZHCx<*Jh~AFq`2vm)eZ58EJEw&G&3Z*<5aOh0XVE z(l!~JD{X#YGuq}Vn=v*&w7J@5tj#qxKe8ETGv4M}n;+X;XLG&H4K_cqnP4-~W|GZM zZEm!gY;%*%%{EhPrrJ!i`I$}DCTDYt&8;@oe`eUsw7JdZcAGnF?zFkfW|qxtn>jW= zx4GM9uFX9*zp$BSGv8)`&Am4F*(|iV-{zM#57;cSS#0wwo4ifI=0Tf>Y?jz8wRza) ze{7c7EVp^Y=24p!HY;s@W3$R;wasHTzqNVXW{u4gHovoZ(q^sAI-93#p0-(U^Nh{! zZHhJ}n`dpFv)N$tyv-kMUa;9@v)SfFo0n`}wt2%v-#ZS-!=zr4%vKR^B)aw6!_L=2)BKY}(lzZ}SzK_BI`CPO$l^ zP2477bE3^jHXUs`*>tw~noSp*t~MvzeBI_0n{GDUZBDg0&8COV={Dc6Im4!>&6zge zv^mSBm(AHW=h*bN>0@)Q&3QISo0QG@Hs7-8Ytzr>0-JB!TxiqZ<|3Q#*bJ~4Xmhd6 zB{qX>2HOm=`L4}Sn_)J?Z7#JLVKdU^GMn$&jIz1h<_eqd+oWwWHdosGz-F|~RW@U6 zerR*G%~+djY<^@j&St#LwKhMtxz6T#n;UF?Vl%;JqRk|mpV~~exyj~cn<+L^ZKm1$ z%qDA-v$@6QR-5TIGi+ws+-7sT%^fy(+T3L`%VxIC9Gjop+-)=0<{q10*vzw;Z?nMW zUYq-D7TVlz^GlltY!=xpw)vG!-lkylpv^-zOKg_fJZ$qnHp^_5+dN|PYnw-HR@khx z`Hjsgo7Fat+5Fb#aho+ZPuTp<=1H5iHtTGjvU%EOz0ET=zqcvclx&{0dCq2o%|@H& zZT?{Mg3TtI%{DLEykzsT%_}y4wAo^_)n=Q`t2VFMY`1yc=1(?n*zB;`Y4c~BH*Mas zdE4eMHf5WN%{w-KwRzWOm(6=Nf3tbtX1C2Ao4?z9V6)feLz{otd}OoFX1~opZ9cX+ zVDpL1zid9W`OM~Xn}6FJv^iw+h0T9#f|$MbZDKZcHuW|QHjQi=+cdFhYSYZ-2%95q zn%lInIm+f}o0c}MY+Bp2v1w~_jLoq&$Jw;AIo{?gHtlUX*qmVVRhzg?!sbMqlWaQL zbh7De^EI0;HeGE_w)wivDK_0~y4##;bDB*Lo6~K+VRME}Pn$DszG-uoO)s0XZO*aj zZPUl*T$}T3k~S%u^KHIm)7Pe-%>_2!wz<%zzs*H9-?15BbFs}OHiK*i+YGV!uFX)J zVK&2UF0~n9Gt%ZVoA23-vbo&m3Y+iSq-`=bSK9o*X0**!He+mlXmhpASet8Xeq=Mw zX1vX{Hb1tx&gOcX8*F}JGr?w}%_N(j+T3V!lg-UGQ*5T%Otbl!P1Yu7bBoQbHq&ip z*vz!K&E|HSJ8bT>xyxpj&1{=FHb1wy+h(rKJvP6vnP)TKW`WJUHuu>qw7K8rmo^XB zEV5Z_^DCRYO~K|tn}=+c*etbqJeCdC*gRqLJDVqM*4nJIdCKN#oAox&*!qpz?#E2=_9}b2iVFr$c*E_bg&t%bl<~n}GJ|jiG!|~{# zP0%6{+NaYt!3oA*_8Bc&Zy(%j-+c`V+6R~0cdeHN9r2EM``|eH>B=>Bd&FFDjQuq1 zCi{B2z&>T??6X$%IK9FukLLEd_Vq6E!Qo~0sri@o^=Z3(clEG+-i_`*6pn(U;W#(} zuKkifvF|SaW}m;R+-sjZpSSNWJ~Kv-*ERf!{j{SY>>7@~p!%C|li7dn%Z|8x_t?VT zv97Vs-r=m^-*(&s!nxsA`_w-;To`V%@3!r~$%Y+9+iymN%fmbDev`s^!8!KbXtVGE zwp+q>k8=Ct-2ODTZ{YTqxcxP5f0Nta<@OJ_{bO!F7@TL{m7Wxf&gcgnJYxoyCuM^Fq{v*R^`!->8|MqxCcna(tPP6Uai1iLH=n)3x*cst8 z+wTXlzHa?sEV^FJ>IR4ZWB=7>MBOO18y)`LzRNwnezZHEgN)Vv9kkBY);Y4iO&DE| zd0dxA;m7bzxB+erciHpdmHI+B+pg!x`f=`Yk7(E$zF^O*mJJh}e`cRWZgbaZLYT1c zo(G3hxL+askNq@uq-}q$efMAG5%zeWG!(+K%|Efnxxt>d2ODN_|ERlHqtQG+qWdQs z&8yk(wT&M5((kp$Tij?#c&u@2qetECy&66KC8Ohi^vm<`O22By)8UlilG&yG|{d zzRC4@m&bjV$9;|czQ%Qlo-f^-zWe2Vs@E^H&lA6~$4NCUgpb-!r$^Sm7Cz+s1Gm3R zn{ME`ZQyxhZqK(L*zM8t>4UJFUH4|;zVJT#sl$Y(`@)T<1i?+ls^^S3`{~qeO+TjZ zWBQ`=G^qY#dZO!+Y*Yv*TUYgZ&9UPgS^t^q2<>xQ^>y5-nVoOD&I7^^*nboI4)?-l zP3-%`f%fO!!H{Mx?0dpX?a%vy(RRxl!Hi}{+IJLp+P|m5pWC00yFZ@{=h?qM9WJy# zd&d^rpUK!m_UCo6W%lQU*b4je#@J)_XMJ6Dt0r~T`yW|Xz5f~Rmb2U~z1=Ozp}(`+ z`wp#cn;u%d&*-5~+n-ktE!m&rhCXk9UgvI~F!V+H_a#HOSk+5Ix7(jvhVHaKUmN-t zyZwWq@7llj9=6;5Jvr<{`?K$`f7+iH4*S&p95C#V{W)mZ@0um-=SepQBaZEEJR+Fa ztjgBG{D0NsiwLu6H~qxYhAn!QGDE3CfP&4fZ;IFZk5)2k=Kh zi(^yvIMw}q%lH%9U)p%9@dtR7XPala{Z?+TvU+=-+aEGkdyBZe6r5*`)yK`jTe&@t z7hn;VU>R26UyXMKnPcsTh3@e}7>5a%gejPY8JLARc&oA6oyQBX2+O*?F4}JiR$y2k z-JXCcn1@AJh81W(B#n*(#$f^`VG3qo4i;bq+7I#FafHU|`A)zT%)lHhz>bZB;QZ=! zGgh}N!=OpzahQZDn1)$chRLSUc4?S}d02+_AIaSQk}w6+FavY2086j}!y}@{Nx&4$ zz#J^V60E?`ep}0(FPMTEn1cmaf)yCrZ>zfPVbWNAKBe#s%)&e@!ZHk7@Hj99GcX4W zummeGJc{jM3T9vq7GMchU}*p4$sGqw!8FXk9L&Q4EW#2j!=PpKxJj6X8JLARSb#-X zf@K)AiXJZx6EF!=Fby*>3v)0J3$O@Funa5E-h|!x31J*2U=pTa8fIV-+Q)GBIB}SS zX_$don1gv(fJIn>WmthhTY6y}CSVe#U>as%7Up0c7GMchV0cWlzXVLd49vj-EWrv4 zk7aw9g7%K@&Q}f=U

Veq40F0xZD_4BPSeFa4?J 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­|tknpgw;JO(s|mhfHN{e^8BVa8 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

s;y^hd?30jMR;MuJ@0$`(7G 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

LtGw_L5qId&whXdr7}3y=2{M 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�Dxu2HPE8~ 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

wBLAH2w+{?h-(8 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 literal 0 HcmV?d00001 diff --git a/Assets/Plugins/OpenJpegDotNet.dll.meta b/Assets/Plugins/OpenJpegDotNet.dll.meta new file mode 100644 index 0000000..30fc99f --- /dev/null +++ b/Assets/Plugins/OpenJpegDotNet.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: b40d6b883361a0d40921720d88966596 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/OpenJpegDotNet.xml b/Assets/Plugins/OpenJpegDotNet.xml new file mode 100644 index 0000000..8078335 --- /dev/null +++ b/Assets/Plugins/OpenJpegDotNet.xml @@ -0,0 +1,1487 @@ + + + + OpenJpegDotNet + + + +

+ Specifies the Digital cinema operation mode. + + + + + Specifies that not Digital Cinema. + + + + + Specifies that the 2K Digital Cinema at 24 fps. + + + + + Specifies that the 2K Digital Cinema at 48 fps. + + + + + Specifies that the 4K Digital Cinema at 24 fps. + + + + + Defines the JPEG 2000 codec V2. This class cannot be inherited. + + + + + Releases all unmanaged resources. + + + + + Specifies the supported codec. + + + + + Specifies that the place-holder. + + + + + Specifies that the JPEG 2000 codestream. + + + + + Specifies that the JPT-stream (JPEG 2000, JPIP). + + + + + Specifies that the JP2 file format. + + + + + Specifies that the JPP-stream (JPEG 2000, JPIP). + + + + + Specifies that the JPX file format(JPEG 2000 Part-2). + + + + + Specifies the Supported image color spaces. + + + + + Specifies that not supported by the library. + + + + + Specifies that not specified in the codestream. + + + + + Specifies that the sRGB. + + + + + Specifies that the Grayscale. + + + + + Specifies that the sYCC. + + + + + Specifies that the e-YCC. + + + + + Specifies that the CMYK. + + + + + Defines the compression parameters. This class cannot be inherited. + + + + + Initializes a new instance of the class. + + + + + Sets or get the error protection methods for Tile Part Headers (0,1,16,32,37-128). + + This object is disposed. + is invalid length. + + + + Sets or get the tile number of header protection specification (>=0). + + This object is disposed. + is invalid length. + + + + Sets or get the error protection methods for packets (0,1,16,32,37-128). + + This object is disposed. + is invalid length. + + + + Sets or get the packet number of packet protection specification (>=0). + + This object is disposed. + is invalid length. + + + + Sets or get the tile number of packet protection specification (>=0). + + This object is disposed. + is invalid length. + + + + Sets or get the sensitivity methods for Tile Part Headers (-1=no,0-7). + + This object is disposed. + is invalid length. + + + + Sets or get the tile number of sensitivity specification (>=0). + + This object is disposed. + is invalid length. + + + + Sets or get the initial precinct height. + + This object is disposed. + is invalid length. + + + + Sets or get the initial precinct width. + + This object is disposed. + is invalid length. + + + + Sets or get the different PSNR (Peak signal-to-noise ratio) for successive layers. + + This object is disposed. + is invalid length. + + + + Sets or get the rates of layers - might be subsequently limited by the max_cs_size field. + + This object is disposed. + is invalid length. + + + + Sets or get the initial code block height. + + This object is disposed. + + + + Sets or get the initial code block width. + + This object is disposed. + + + + Sets or get the output file format. + + This object is disposed. + + + + Sets or get the Digital Cinema compliance. + + if 0 not compliant, 1 is compliant. + This object is disposed. + + + + Sets or get the RSIZ. + + This object is disposed. + + + + Sets or get the allocation by rate/distortion. + + This object is disposed. + + + + Sets or get the allocation by fixed layer. + + This object is disposed. + + + + Sets or get the add fixed quality. + + This object is disposed. + + + + Sets or get the regions size + + This object is disposed. + + + + Sets or get the XTsiz. + + This object is disposed. + + + + Sets or get the YTsiz. + + This object is disposed. + + + + Sets or get the XTOsiz. + + This object is disposed. + + + + Sets or get the YTOsiz. + + This object is disposed. + + + + Sets or get the coding style. + + This object is disposed. + + + + Sets or get the input file format. + + This object is disposed. + + + + Sets or get the subimage encoding: origin image offset in x direction. + + This object is disposed. + + + + Sets or get the subimage encoding: origin image offset in y direction. + + This object is disposed. + + + + Sets or get the index generation. + + This object is disposed. + + + + Sets or get the value to indicate whether irreversible or not for compression. + + true if use the irreversible DWT 9-7; otherwise, use lossless compression is false. + This object is disposed. + + + + Sets or get the error protection method for Main Header (0,1,16,32,37-128). + + This object is disposed. + + + + Sets or get the sensitivity addressing size (0=auto/2/4 bytes). + + This object is disposed. + + + + Sets or get the sensitivity method for Main Header (-1=no,0-7). + + This object is disposed. + + + + Sets or get the sensitivity range (0-3). + + This object is disposed. + + + + Sets or get the enables writing of ESD, (0=no/1/2 bytes). + + This object is disposed. + + + + Sets or get the maximum size (in bytes) for each component. + + Component size limitation is not considered if 0. + This object is disposed. + + + + Sets or get the maximum size (in bytes) for the whole codestream. + + If 0, codestream size limitation is not considered If it does not comply with , prevails and a warning is issued. + This object is disposed. + + + + Sets or get the mode switch (codeblock coding style). + + This object is disposed. + + + + Sets or get the number of progression order changes (POC). + + This object is disposed. + + + + Sets or get the number of resolutions. + + This object is disposed. + + + + Sets or get the progression order. + + This object is disposed. + + + + Sets or get the number of precinct size specifications. + + This object is disposed. + + + + Sets or get the region of interest: affected component in [0..3], -1 means no ROI. + + This object is disposed. + + + + Sets or get the region of interest: upshift value. + + This object is disposed. + + + + Sets or get the subsampling value for dx. + + This object is disposed. + + + + Sets or get the subsampling value for dy. + + This object is disposed. + + + + Sets or get the number of layers. + + This object is disposed. + + + + Sets or get whether enables JPIP indexing. + + This object is disposed. + + + + Sets or get whether enables writing of EPC in MH, thus activating JPWL. + + This object is disposed. + + + + Sets or get the MCT (multiple component transform). + + This object is disposed. + + + + Sets or get whether enables size of tile. + + This object is disposed. + + + + Sets or get the flag for Tile part generation. + + This object is disposed. + + + + Sets or get the Tile part generation. + + This object is disposed. + + + + Releases all unmanaged resources. + + + + + Defines the decompression parameters. This class cannot be inherited. + + + + + Initializes a new instance of the class. + + + + + Sets or get the output file format. + + This object is disposed. + + + + Sets or get the maximum number of quality layers to decode. + + This object is disposed. + + + + Sets or get the number of highest resolution levels to be discarded. + + This object is disposed. + + + + Sets or get the input file format. + + This object is disposed. + + + + Sets or get the decoding area left boundary. + + This object is disposed. + + + + Sets or get the decoding area right boundary. + + This object is disposed. + + + + Sets or get the decoding area up boundary. + + This object is disposed. + + + + Sets or get the decoding area bottom boundary. + + This object is disposed. + + + + Sets or get the flags. + + This object is disposed. + + + + Sets or get whether activates the JPWL correction capabilities for JPWL (wireless applications). + + This object is disposed. + + + + Sets or get the expected number of components for JPWL (wireless applications). + + This object is disposed. + + + + Sets or get the maximum number of tiles for JPWL (wireless applications). + + This object is disposed. + + + + Sets or get the number of blocks of tile to decode. + + This object is disposed. + + + + Sets or get the tile number of the decoded tile. + + This object is disposed. + + + + Sets or get whether enables verbose mode. + + This object is disposed. + + + + Releases all unmanaged resources. + + + + + Callback function prototype for read. + + The event message. + The client object where will be return the event message. + + + + Callback function prototype for read. + + The position to read. + The length of . + The data pointer to have beee passed to . + + + + Callback function prototype for write. + + The position to write. + The length of . + The data pointer to have beee passed to . + + + + Callback function prototype for skip. + + The position to skip. + The data pointer to have beee passed to . + + + + Callback function prototype for seek. + + The position to seek. + The data pointer to have beee passed to . + + + + Callback function to free user data. + + The data pointer to have beee passed to . + + + + Specifies the supported codec. + + + + + Specifies that the place-holder. + + + + + Specifies that the JPEG 2000 codestream. + + + + + Specifies that the JP2 file format. + + + + + Specifies that the JPT-stream (JPEG 2000, JPIP). + + + + + Specifies that the Pixelmator file format. + + + + + Specifies that the PGX (JPEG 2000) file format. + + + + + Specifies that the Bitmap file format. + + + + + Specifies that the YUV file format. + + + + + Specifies that the Tagged Image file format. + + + + + Specifies that the Raw (big endian) Image file format. + + + + + Specifies that the Truevision Graphics Adapter file format. + + + + + Specifies that the Portable Network Graphics file format. + + + + + Specifies that the Raw (little endian) Image file format. + + + + + Defines the image data and characteristics. This class cannot be inherited. + + + + + Gets or sets the horizontal offset from the origin of the reference grid to the left side of the image area. + + This object is disposed. + + + + Gets or sets the width of the reference grid. + + This object is disposed. + + + + Gets or sets the vertical offset from the origin of the reference grid to the top side of the image area. + + This object is disposed. + + + + Gets or sets the height of the reference grid. + + This object is disposed. + + + + Gets the number of components in the image. + + This object is disposed. + + + + Gets or sets the color space. + + This object is disposed. + + + + Gets the image components. + + This object is disposed. + + + + Gets the 'restricted' ICC profile. + + This object is disposed. + + + + Converts this to a GDI+ . + + Export alpha channel + A that represents the converted . + This object is disposed. + This object is not supported. + + + + Converts this to a Bitmap . + + Export alpha channel + A that represents the converted . + This object is not supported. + + + + Converts this to a TARGA . + + A that represents the converted . + This object is not supported. + + + + Releases all unmanaged resources. + + + + + Defines the image component. This class cannot be inherited. + + + + + Get the alpha channel. + + This object is disposed. + + + + Get the image depth in bits. + + This object is disposed. + + + + Get the image component data. + + This object is disposed. + + + + Get the horizontal separation of a sample of ith component with respect to the reference grid. + + This object is disposed. + + + + Get the vertical separation of a sample of ith component with respect to the reference grid. + + This object is disposed. + + + + Get the number of division by 2 of the out image compared to the original size of image. + + This object is disposed. + + + + Get the data height. + + This object is disposed. + + + + Get the precision. + + This object is disposed. + + + + Get the number of decoded resolution. + + This object is disposed. + + + + Get the value to indicate whether data with signed or unsigned. + + true if data with signed; otherwise, false. + This object is disposed. + + + + Get the data width. + + This object is disposed. + + + + Get the x component offset compared to the whole image. + + This object is disposed. + + + + Get the y component offset compared to the whole image. + + This object is disposed. + + + + Releases all unmanaged resources. + + + + + Defines the component parameters. This class cannot be inherited. + + + + + Initializes a new instance of the class. + + + + + Sets or get the image depth in bits. + + This object is disposed. + + + + Sets or get the horizontal separation of a sample of ith component with respect to the reference grid. + + This object is disposed. + + + + Sets or get the vertical separation of a sample of ith component with respect to the reference grid. + + This object is disposed. + + + + Sets or get the data height. + + This object is disposed. + + + + Sets or get the precision. + + This object is disposed. + + + + Sets or get the value to indicate whether data with signed or unsigned. + + true if data with signed; otherwise, false. + This object is disposed. + + + + Sets or get the data width. + + This object is disposed. + + + + Sets or get the x component offset compared to the whole image. + + This object is disposed. + + + + Sets or get the y component offset compared to the whole image. + + This object is disposed. + + + + Releases all unmanaged resources. + + + + + Gets a value indicating whether this instance has been disposed. + + true if this instance has been disposed; otherwise, false. + + + + Releases all resources used by this . + + + + + Releases all resources used by this . + + Indicate value whether method was called. + + + + Gets a value indicating whether this instance has been disposed. + + true if this instance has been disposed; otherwise, false. + + + + Releases all resources used by this . + + + + + Releases all resources used by this . + + Indicate value whether method was called. + + + + Provides the methods of OpenJpeg. + + + Provides the methods of OpenJpeg. + + + + + Creates a J2K/JP2 compression structure. + + The codec format to create. + The . + + + + Start to compress the current image. + + the Jpeg 2000 codec to compress. + The image that receives encoded datum. + The Jpeg 2000 stream. + true if the start to compress is correctly set; otherwise, false. + , or is null. + , or is disposed. + + + + End to compress the current image. + + the Jpeg 2000 codec to compress. + The Jpeg 2000 stream. + true if the end to compress is correctly set; otherwise, false. + or is null. + or is disposed. + + + + Encode an image into a JPEG-2000 codestream. + + the Jpeg 2000 codec to compress. + The Jpeg 2000 stream. + true if the end to compress is correctly set; otherwise, false. + or is null. + or is disposed. + + + + Set encoding parameters to default values. + + The to compress image. + is null. + is disposed. + + + + Setup the encoder with compression parameters provided by the user and with the message handler provided by the user. + + The to compress image. + The for image compression. + Input filled image. + true if the decoder is correctly set; otherwise, false. + , or is null. + , or is disposed. + + + + Creates a J2K/JP2 decompression structure. + + The codec format to create. + The . + + + + Decode an image from a JPEG 2000 codestream. + + the Jpeg 2000 codec to read. + The Jpeg 2000 stream. + The image that receives decoded datum. + true if success; otherwise, false. + , or is null. + , or is disposed. + + + + Read after the codestream if necessary. + + the Jpeg 2000 codec to read. + The Jpeg 2000 stream. + true if success; otherwise, false. + or is null. + or is disposed. + + + + Decodes an image header. + + The Jpeg 2000 stream. + the Jpeg 2000 codec to read. + When this method returns, contains the read from stream. This parameter is passed uninitialized. + true if the main header of the codestream and the JP2 header is correctly read; otherwise, false. + or is null. + or is disposed. + + + + Sets the given area to be decoded. + + The Jpeg 2000 codec. + The decoded previously set by . + The left position of the rectangle to decode in image. + The top position of the rectangle to decode in image. + The right position of the rectangle to decode in image. + The bottom position of the rectangle to decode in image. + true if the area is correctly set; otherwise, false. + or is null. + or is disposed. + + + + Set decoding parameters to default values. + + The to decompress image. + is null. + is disposed. + + + + Setup the decoder with decompression parameters provided by the user and with the message handler provided by the user. + + The to decompress image. + The to decompress image. + true if the decoder is correctly set; otherwise, false. + or is null. + or is disposed. + + + + Allocates worker threads for the compressor/decompressor. + + The to compress or decompress image. + The number of threads. + true if the function succeeds; otherwise, false. + or is null. + or is disposed. + This function must be called after and before for the decoding side, or after () and before for the encoding side. + + + + Writes a tile with the given data. + + The to write tile. + The index of the tile to write. + The buffer to the data to write. + The length of datum to write. This value os used to make sure the data being written is correct. + The stream to write data to. + true if the data could be written; otherwise, false. + or is null. + or is disposed. + + + + Get the string representation of the OpenJpeg version. + + The string representation of the OpenJpeg version. + + + + Get the string representation of the version of wrapper library of OpenJpeg. + + The string representation of the version of wrapper library of OpenJpeg. + + + + Set the info handler. + + The codec previously initialise. + The callback function which will be used. + The client object where will be returned the message. + or is null. + is disposed. + + + + Set the warning handler. + + The codec previously initialise. + The callback function which will be used. + The client object where will be returned the message. + or is null. + is disposed. + + + + Set the warning handler. + + The codec previously initialise. + The callback function which will be used. + The client object where will be returned the message. + or is null. + is disposed. + + + + Create an image. + + The number of components. + The components parameters. + The image color space. + A new . + + + + Creates an image without allocating memory for the image. + + The number of components. + The components parameters. + The image color space. + A new . + + + + Allocates new memory buffer for . + + The number of bytes to allocate. + The new pointer to indicate allocated memory buffer if success; otherwise, . + + + + Unallocates memory buffer for . + + The pointer to indicate allocated memory buffer by . + + + + Sets the MCT matrix to use. + + The to change. + The encoding matrix. + The dc shift coefficients to use. + The number of components of the image. + true if the parameters could be set; otherwise, false. + , or is null. + is disposed. + The length of must be x or less. Or The length of must be or less + + + + Creates an abstract stream. + + The value whether the stream is a read stream. + The . + + + + Creates an abstract stream with a specific buffer size. + + The size of the chunk used to stream. + The value whether the stream is a read stream. + The . + + + + Sets the given callback to be used as a read function. + + The stream to modify. + The callback to free user data when reads data stream. + or is null. + is disposed. + + + + Sets the given callback to be used as a write function. + + The stream to modify. + The callback to free user data when writes data stream. + or is null. + is disposed. + + + + Sets the given callback to be used as a skip function. + + The stream to modify. + The callback to free user data when skips data stream. + or is null. + is disposed. + + + + Sets the given callback to be used as a seek function, the stream is then seekable, + + The stream to modify. + The callback to free user data when seeks data stream. + or is null. + is disposed. + + + + Sets the given data to be used as a user data for the stream. + + The stream to modify. + The data to set. + The callback to free user data when is disposed. + or is null. + is disposed. + + + + Sets the length of the user data for the stream. + + The stream to modify. + The length of the user_data. + is null. + is disposed. + + + + Create a stream from a file identified with its filename with default parameters. + + The filename of the file to input to stream. + The value whether the stream is a read stream. + The . + is null. + The specified path does not exist. + + + + Create a stream from a file identified with its filename with a specific buffer size. + + The filename of the file to input to stream. + The size of the chunk used to stream. + The value whether the stream is a read stream. + The . + is null. + The specified path does not exist. + + + + Returns if the library is built with thread support. + + true if mutex, condition, thread, thread pool are available; otherwise, false. + + + + Return the number of virtual CPUs. + + The number of virtual CPUs. + + + + A class which has a pointer of native structure. + + + + + Initializes a new instance of the class with the specified value indicating whether this instance is disposable. + + true if this instance is disposable; otherwise, false. + + + + Gets a value indicating whether this instance has been disposed. + + true if this instance has been disposed; otherwise, false. + + + + Gets a value indicating whether this instance is disposable. + + true if this instance is disposable; otherwise, false. + + + + Gets a pointer of native structure. + > + + + + If this object is disposed, then is thrown. + + + + + Determines whether this instance and another specified object have the same value. + + The to compare to this instance. + true if the value of the parameter is the same as the value of this instance; otherwise, false. If is null, the method returns false. + + + + Determines whether this instance and a specified object, which must also be a object, have the same value. + + The to compare to this instance. + true if is a and its value is the same as this instance; otherwise, false. If is null, the method returns false. + + + + Returns the hash code for this string. + + A 32-bit signed integer hash code. + + + + Releases all managed resources. + + + + + Releases all unmanaged resources. + + + + + Releases all resources used by this . + + + + + Releases all resources used by this . + + Indicate value whether method was called. + + + + Specifies the progression order. + + + + + Specifies that place-holder. + + + + + Specifies that layer-resolution-component-precinct order. + + + + + Specifies that resolution-layer-component-precinct order. + + + + + Specifies that resolution-precinct-component-layer order. + + + + + Specifies that precinct-component-resolution-layer order. + + + + + Specifies that component-precinct-resolution-layer order. + + + + + Defines the raw bitmap image data. This class cannot be inherited. + + + + + Gets the number of channels, of this . + + The number of channels, of this . + + + + Gets the pixel data of this . + + The pixel data. + + + + Gets the height, in pixels, of this . + + The height, in pixels, of this . + + + + Gets the width, in pixels, of this . + + The width, in pixels, of this . + + + + Gets the specified for the object. + + The type of image storeed as + + + + Specifies the region size capabilities. + + + + + Specifies that Standard JPEG 2000 profile. + + + + + Specifies that Profile name for a 2K image. + + + + + Specifies that Profile name for a 4K image. + + + + + Specifies that multiple component transformation. + + + + + A stream that represents a JPEG 2000. This class cannot be inherited. + + + + + Releases all unmanaged resources. + + + + diff --git a/Assets/Plugins/OpenJpegDotNet.xml.meta b/Assets/Plugins/OpenJpegDotNet.xml.meta new file mode 100644 index 0000000..c15d75c --- /dev/null +++ b/Assets/Plugins/OpenJpegDotNet.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ef87fb698153618479434b8b658dc0a4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: