public void WriteUUID(UUID val) { if (IsZeroEncoded) { byte[] buf = new byte[16]; val.ToBytes(buf, 0); WriteZeroEncoded(buf); } else { val.ToBytes(Data, DataPos); DataPos += 16; DataLength = DataPos; } }
private static UUID deMoronize(UUID id) { byte[] data = new byte[16]; id.ToBytes(data, 0); for (int i = 0; i < 16; ++i) { data[i] ^= moronize[i]; } return(new UUID(data, 0)); }
public override void ToBytes(byte[] bytes, ref int i) { Position.ToBytes(bytes, i); i += sizeof(float) * 3; PositionDelta.ToBytes(bytes, i); i += sizeof(float) * 3; LookAt.ToBytes(bytes, i); i += sizeof(float) * 3; LookAtDelta.ToBytes(bytes, i); i += sizeof(float) * 3; Up.ToBytes(bytes, i); i += sizeof(float) * 3; Utils.UIntToBytes(TickLength, bytes, i); i += sizeof(int); Source.ToBytes(bytes, i); }
public static bool SaveCachedImage(byte[] tgaData, UUID textureID, bool hasAlpha, bool fullAlpha, bool isMask) { try { string fname = System.IO.Path.Combine(Login.Client.Settings.ASSET_CACHE_DIR, string.Format("{0}.rzi", textureID)); //string fname = System.IO.Path.Combine(".", string.Format("{0}.rzi", textureID)); using (var f = File.Open(fname, FileMode.Create, FileAccess.Write, FileShare.None)) { int i = 0; // magic header f.Write(Utils.StringToBytes(RAD_IMG_MAGIC), 0, RAD_IMG_MAGIC.Length); i += RAD_IMG_MAGIC.Length; // version f.WriteByte((byte)1); i++; // texture info f.WriteByte(hasAlpha ? (byte)1 : (byte)0); f.WriteByte(fullAlpha ? (byte)1 : (byte)0); f.WriteByte(isMask ? (byte)1 : (byte)0); i += 3; // texture size byte[] uncompressedSize = Utils.IntToBytes(tgaData.Length); f.Write(uncompressedSize, 0, uncompressedSize.Length); i += uncompressedSize.Length; // texture id byte[] id = new byte[16]; textureID.ToBytes(id, 0); f.Write(id, 0, 16); i += 16; // compressed texture data using (var compressed = new DeflateStream(f, CompressionMode.Compress)) { compressed.Write(tgaData, 0, tgaData.Length); } } return(true); } catch (Exception ex) { Logger.DebugLog(string.Format("Failed to save radegast cache file {0}: {1}", textureID, ex.Message)); return(false); } }
private void TransferNotFound(LLAgent agent, UUID transferID, UUID assetID, AssetType type) { m_log.Info("TransferNotFound for asset " + assetID + " with type " + type); TransferInfoPacket response = new TransferInfoPacket(); response.TransferInfo = new TransferInfoPacket.TransferInfoBlock(); response.TransferInfo.TransferID = transferID; // Set the response channel type response.TransferInfo.ChannelType = (int)ChannelType.Asset; // Params response.TransferInfo.Params = new byte[20]; assetID.ToBytes(response.TransferInfo.Params, 0); Utils.IntToBytes((int)type, response.TransferInfo.Params, 16); response.TransferInfo.Size = 0; response.TransferInfo.Status = (int)StatusCode.UnknownSource; response.TransferInfo.TargetType = (int)TargetType.Unknown; m_udp.SendPacket(agent, response, ThrottleCategory.Asset, false); }
private void TransferDownload(LLAgent agent, UUID transferID, UUID assetID, AssetType type, Asset asset) { const int MAX_CHUNK_SIZE = 1000; string contentType = LLUtil.LLAssetTypeToContentType((int)type); if (contentType == asset.ContentType) { m_log.Debug(String.Format("Transferring asset {0} ({1})", asset.ID, asset.ContentType)); TransferInfoPacket response = new TransferInfoPacket(); response.TransferInfo = new TransferInfoPacket.TransferInfoBlock(); response.TransferInfo.TransferID = transferID; // Set the response channel type response.TransferInfo.ChannelType = (int)ChannelType.Asset; // Params response.TransferInfo.Params = new byte[20]; assetID.ToBytes(response.TransferInfo.Params, 0); Utils.IntToBytes((int)type, response.TransferInfo.Params, 16); response.TransferInfo.Size = asset.Data.Length; response.TransferInfo.Status = (int)StatusCode.OK; response.TransferInfo.TargetType = (int)TargetType.Unknown; // Doesn't seem to be used by the client m_udp.SendPacket(agent, response, ThrottleCategory.Asset, false); // Transfer system does not wait for ACKs, just sends all of the // packets for this transfer out int processedLength = 0; int packetNum = 0; while (processedLength < asset.Data.Length) { TransferPacketPacket transfer = new TransferPacketPacket(); transfer.TransferData.ChannelType = (int)ChannelType.Asset; transfer.TransferData.TransferID = transferID; transfer.TransferData.Packet = packetNum++; int chunkSize = Math.Min(asset.Data.Length - processedLength, MAX_CHUNK_SIZE); transfer.TransferData.Data = new byte[chunkSize]; Buffer.BlockCopy(asset.Data, processedLength, transfer.TransferData.Data, 0, chunkSize); processedLength += chunkSize; if (processedLength >= asset.Data.Length) { transfer.TransferData.Status = (int)StatusCode.Done; } else { transfer.TransferData.Status = (int)StatusCode.OK; } m_udp.SendPacket(agent, transfer, ThrottleCategory.Asset, false); } } else { m_log.WarnFormat("Request for asset {0} with type {1} does not match actual asset type {2}", assetID, type, asset.ContentType); TransferNotFound(agent, transferID, assetID, type); } }
private void TransferDownload(LLAgent agent, UUID transferID, UUID assetID, AssetType type, Asset asset) { const int MAX_CHUNK_SIZE = 1000; string contentType = LLUtil.LLAssetTypeToContentType((int)type); if (contentType == asset.ContentType) { m_log.Debug(String.Format("Transferring asset {0} ({1})", asset.ID, asset.ContentType)); TransferInfoPacket response = new TransferInfoPacket(); response.TransferInfo = new TransferInfoPacket.TransferInfoBlock(); response.TransferInfo.TransferID = transferID; // Set the response channel type response.TransferInfo.ChannelType = (int)ChannelType.Asset; // Params response.TransferInfo.Params = new byte[20]; assetID.ToBytes(response.TransferInfo.Params, 0); Utils.IntToBytes((int)type, response.TransferInfo.Params, 16); response.TransferInfo.Size = asset.Data.Length; response.TransferInfo.Status = (int)StatusCode.OK; response.TransferInfo.TargetType = (int)TargetType.Unknown; // Doesn't seem to be used by the client m_udp.SendPacket(agent, response, ThrottleCategory.Asset, false); // Transfer system does not wait for ACKs, just sends all of the // packets for this transfer out int processedLength = 0; int packetNum = 0; while (processedLength < asset.Data.Length) { TransferPacketPacket transfer = new TransferPacketPacket(); transfer.TransferData.ChannelType = (int)ChannelType.Asset; transfer.TransferData.TransferID = transferID; transfer.TransferData.Packet = packetNum++; int chunkSize = Math.Min(asset.Data.Length - processedLength, MAX_CHUNK_SIZE); transfer.TransferData.Data = new byte[chunkSize]; Buffer.BlockCopy(asset.Data, processedLength, transfer.TransferData.Data, 0, chunkSize); processedLength += chunkSize; if (processedLength >= asset.Data.Length) transfer.TransferData.Status = (int)StatusCode.Done; else transfer.TransferData.Status = (int)StatusCode.OK; m_udp.SendPacket(agent, transfer, ThrottleCategory.Asset, false); } } else { m_log.WarnFormat("Request for asset {0} with type {1} does not match actual asset type {2}", assetID, type, asset.ContentType); TransferNotFound(agent, transferID, assetID, type); } }
public void AddUUID(UUID v) { v.ToBytes(m_tmp, 0); AddBytes(m_tmp, 16); }
public string compressUUID(UUID input) { byte[] compressed = new byte[16]; char[] compressedC = new char[16]; input.ToBytes(compressed, 0); for (int x = 0; x < 16; x++) { compressedC[x] = (char)compressed[x]; } return (new string(compressedC, 0, 16)); }
private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); // Group invitations if ((im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) || (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline)) { UUID inviteID = new UUID(im.imSessionID); GroupInviteInfo inviteInfo = m_groupData.GetAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID); if (inviteInfo == null) { if (m_debugEnabled) m_log.WarnFormat("[GROUPS]: Received an Invite IM for an invite that does not exist {0}.", inviteID); return; } if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Invite is for Agent {0} to Group {1}.", inviteInfo.AgentID, inviteInfo.GroupID); UUID fromAgentID = new UUID(im.fromAgentID); if ((inviteInfo != null) && (fromAgentID == inviteInfo.AgentID)) { // Accept if (im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received an accept invite notice."); // and the sessionid is the role m_groupData.AddAgentToGroup(GetRequestingAgentID(remoteClient), inviteInfo.AgentID, inviteInfo.GroupID, inviteInfo.RoleID); GridInstantMessage msg = new GridInstantMessage(); msg.imSessionID = UUID.Zero.Guid; msg.fromAgentID = UUID.Zero.Guid; msg.toAgentID = inviteInfo.AgentID.Guid; msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); msg.fromAgentName = "Groups"; msg.message = string.Format("You have been added to the group."); msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageBox; msg.fromGroup = false; msg.offline = (byte)0; msg.ParentEstateID = 0; msg.Position = Vector3.Zero; msg.RegionID = UUID.Zero.Guid; msg.binaryBucket = new byte[0]; OutgoingInstantMessage(msg, inviteInfo.AgentID); //WTH??? noone but the invitee needs to know //UpdateAllClientsWithGroupInfo(inviteInfo.AgentID); SendAgentGroupDataUpdate(remoteClient); // XTODO: If the inviter is still online, they need an agent dataupdate // and maybe group membership updates for the invitee // Reply: why do they need that? they will get told about the new user when they reopen the groups panel m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID); } // Reject if (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received a reject invite notice."); m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID); } } } // Group notices if ((im.dialog == (byte)InstantMessageDialog.GroupNotice)) { if (!m_groupNoticesEnabled) return; UUID GroupID = new UUID(im.toAgentID); if (m_groupData.GetGroupRecord(GetRequestingAgentID(remoteClient), GroupID, null) != null) { UUID NoticeID = UUID.Random(); string Subject = im.message.Substring(0, im.message.IndexOf('|')); string Message = im.message.Substring(Subject.Length + 1); byte[] bucket; UUID ItemID = UUID.Zero; int AssetType = 0; string ItemName = ""; if ((im.binaryBucket.Length == 1) && (im.binaryBucket[0] == 0)) { bucket = new byte[19]; bucket[0] = 0; bucket[1] = 0; GroupID.ToBytes(bucket, 2); bucket[18] = 0; } else { bucket = im.binaryBucket; string binBucket = OpenMetaverse.Utils.BytesToString(im.binaryBucket); binBucket = binBucket.Remove(0, 14).Trim(); OSDMap binBucketOSD = (OSDMap)OSDParser.DeserializeLLSDXml(binBucket); if (binBucketOSD.ContainsKey("item_id")) { ItemID = binBucketOSD["item_id"].AsUUID(); InventoryItemBase item = new InventoryItemBase(ItemID, GetRequestingAgentID(remoteClient)); item = m_sceneList[0].InventoryService.GetItem(item); if (item != null) { AssetType = item.AssetType; ItemName = item.Name; } else ItemID = UUID.Zero; } } m_groupData.AddGroupNotice(GetRequestingAgentID(remoteClient), GroupID, NoticeID, im.fromAgentName, Subject, Message, ItemID, AssetType, ItemName); if (OnNewGroupNotice != null) OnNewGroupNotice(GroupID, NoticeID); // Send notice out to everyone that wants notices foreach (GroupMembersData member in m_groupData.GetGroupMembers(GetRequestingAgentID(remoteClient), GroupID)) { if (m_debugEnabled) { UserAccount targetUser = m_sceneList[0].UserAccountService.GetUserAccount(remoteClient.Scene.RegionInfo.ScopeID, member.AgentID); if (targetUser != null) { m_log.DebugFormat("[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", NoticeID, targetUser.FirstName + " " + targetUser.LastName, member.AcceptNotices); } else { m_log.DebugFormat("[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", NoticeID, member.AgentID, member.AcceptNotices); } } if (member.AcceptNotices) { // Build notice IIM GridInstantMessage msg = CreateGroupNoticeIM(GetRequestingAgentID(remoteClient), NoticeID, (byte)OpenMetaverse.InstantMessageDialog.GroupNotice); msg.toAgentID = member.AgentID.Guid; OutgoingInstantMessage(msg, member.AgentID); } } } } if ((im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryDeclined) || (im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryDeclined)) { GroupAttachmentCache.Remove(im.imSessionID); } if ((im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryAccepted) || (im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryAccepted)) { UUID FolderID = new UUID(im.binaryBucket, 0); InventoryItemBase item = new InventoryItemBase(GroupAttachmentCache[im.imSessionID]); item = ((Scene)remoteClient.Scene).InventoryService.GetItem(item); ILLClientInventory inventoryModule = remoteClient.Scene.RequestModuleInterface<ILLClientInventory>(); if (inventoryModule != null) item = inventoryModule.GiveInventoryItem(remoteClient.AgentId, item.Owner, GroupAttachmentCache[im.imSessionID], FolderID); if (item != null) remoteClient.SendBulkUpdateInventory(item); GroupAttachmentCache.Remove(im.imSessionID); } if ((im.dialog == 210)) { // This is sent from the region that the ejectee was ejected from // if it's being delivered here, then the ejectee is here // so we need to send local updates to the agent. UUID ejecteeID = new UUID(im.toAgentID); im.dialog = (byte)InstantMessageDialog.MessageFromAgent; OutgoingInstantMessage(im, ejecteeID); IClientAPI ejectee = GetActiveClient(ejecteeID); if (ejectee != null) { UUID groupID = new UUID(im.imSessionID); ejectee.SendAgentDropGroup(groupID); } } // Interop, received special 211 code for offline group notice if ((im.dialog == 211)) { im.dialog = (byte)InstantMessageDialog.GroupNotice; //In offline group notices, imSessionID is replaced with the NoticeID so that we can rebuild the packet here GroupNoticeInfo GND = m_groupData.GetGroupNotice(new UUID(im.toAgentID), new UUID(im.imSessionID)); //We reset the ID so that if this was set before, it won't be misadded or anything to the cache im.imSessionID = UUID.Random().Guid; //Rebuild the binary bucket if (GND.noticeData.HasAttachment) { im.binaryBucket = CreateBitBucketForGroupAttachment(GND.noticeData, GND.GroupID); //Save the sessionID for the callback by the client (reject or accept) //Only save if has attachment GroupAttachmentCache[im.imSessionID] = GND.noticeData.ItemID; } else { byte[] bucket = new byte[19]; bucket[0] = 0; //Attachment enabled == false so 0 bucket[1] = 0; //No attachment, so no asset type GND.GroupID.ToBytes(bucket, 2); bucket[18] = 0; //dunno im.binaryBucket = bucket; } OutgoingInstantMessage(im, new UUID(im.toAgentID)); //You MUST reset this, otherwise the client will get it twice, // as it goes through OnGridInstantMessage // which will check and then reresent the notice im.dialog = 211; } }
// This bucket format is the longer database-stored format for binary notice data. private byte[] FormatBucketForStorage(bool HasAttachment, byte AssetType, UUID GroupID, UUID ItemID, UUID NoticeID, string AttachmentName) { byte[] bucket = null; if (HasAttachment) { byte[] nameBytes = OpenMetaverse.Utils.StringToBytes(AttachmentName); int nameLen = nameBytes.GetLength(0); // old bucket byte buffer is bool,invtype,[2]ownerID,[18]itemID,[34]name // new bucket byte buffer is bool,invtype,[2]ownerID,[18]itemID,[34]0x01,[35]version,[36]noticeID,[52]name bucket = new byte[1 + 1 + 16 + 16 + 16 + 1 + 1 + nameLen]; bucket[0] = 1; // HasAttachment boolean bucket[1] = AssetType; // attachment asset/inventory type GroupID.ToBytes(bucket, 2); // 16-byte UUID ItemID.ToBytes(bucket, 18); // 16-byte UUID bucket[34] = (byte)1; // sign of new extended format bucket[35] = (byte)0; // version number of new format NoticeID.ToBytes(bucket, 36); // 16-byte UUID Array.Copy(nameBytes, 0, bucket, ATTACH_NAME_OFFSET, nameLen); // name } else { bucket = new byte[1 + 1 + 16 + 16 + 16 + 1 + 1 + 1]; bucket[0] = 0; // HasAttachment boolean bucket[1] = 0; // attachment type UUID.Zero.ToBytes(bucket, 2); // 16-byte UUID UUID.Zero.ToBytes(bucket, 18); // 16-byte UUID bucket[34] = (byte)1; // sign of new extended format bucket[35] = (byte)0; // version number of new format NoticeID.ToBytes(bucket, 36); // 16-byte UUID bucket[ATTACH_NAME_OFFSET] = 0; // name } return bucket; }
// This bucket format is the NOT database-stored format for binary notice data, but rather the data sent to the viewer in the IM packet. public byte[] FormatBucketForIM(bool HasAttachment, byte AttType, UUID GroupID, string AttName) { byte[] nameBytes = OpenMetaverse.Utils.StringToBytes(" "+AttName); int nameLen = nameBytes.GetLength(0); byte[] bucket = new byte[18+nameLen]; // bucket byte buffer is HasAttachment, invtype, ownerID, name bucket[0] = HasAttachment ? (byte)0x01 : (byte)0x00; bucket[1] = AttType; GroupID.ToBytes(bucket, 2); // 16-byte UUID Array.Copy(nameBytes, 0, bucket, 18, nameLen); // name return bucket; }
public static bool SaveCachedImage(byte[] tgaData, UUID textureID, bool hasAlpha, bool fullAlpha, bool isMask) { try { string fname = System.IO.Path.Combine(RadegastInstance.GlobalInstance.Client.Settings.ASSET_CACHE_DIR, string.Format("{0}.rzi", textureID)); //string fname = System.IO.Path.Combine(".", string.Format("{0}.rzi", textureID)); using (var f = File.Open(fname, FileMode.Create, FileAccess.Write, FileShare.None)) { int i = 0; // magic header f.Write(Utils.StringToBytes(RAD_IMG_MAGIC), 0, RAD_IMG_MAGIC.Length); i += RAD_IMG_MAGIC.Length; // version f.WriteByte((byte)1); i++; // texture info f.WriteByte(hasAlpha ? (byte)1 : (byte)0); f.WriteByte(fullAlpha ? (byte)1 : (byte)0); f.WriteByte(isMask ? (byte)1 : (byte)0); i += 3; // texture size byte[] uncompressedSize = Utils.IntToBytes(tgaData.Length); f.Write(uncompressedSize, 0, uncompressedSize.Length); i += uncompressedSize.Length; // texture id byte[] id = new byte[16]; textureID.ToBytes(id, 0); f.Write(id, 0, 16); i += 16; // compressed texture data using (var compressed = new DeflateStream(f, CompressionMode.Compress)) { compressed.Write(tgaData, 0, tgaData.Length); } } return true; } catch (Exception ex) { Logger.DebugLog(string.Format("Failed to save radegast cache file {0}: {1}", textureID, ex.Message)); return false; } }
public override void ToBytes(byte[] bytes, ref int i) { ObjectID.ToBytes(bytes, i); i += 16; }
private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); // Group invitations if ((im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) || (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline)) { UUID inviteID = new UUID(im.imSessionID); GroupInviteInfo inviteInfo = m_groupData.GetAgentToGroupInvite(GetClientGroupRequestID(remoteClient), inviteID); if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Invite is for Agent {0} to Group {1}.", inviteInfo.AgentID, inviteInfo.GroupID); UUID fromAgentID = new UUID(im.fromAgentID); if ((inviteInfo != null) && (fromAgentID == inviteInfo.AgentID)) { // Accept if (im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received an accept invite notice."); // and the sessionid is the role m_groupData.AddAgentToGroup(GetClientGroupRequestID(remoteClient), inviteInfo.AgentID, inviteInfo.GroupID, inviteInfo.RoleID, false); GridInstantMessage msg = new GridInstantMessage(); msg.imSessionID = UUID.Zero.Guid; msg.fromAgentID = UUID.Zero.Guid; msg.toAgentID = inviteInfo.AgentID.Guid; msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); msg.fromAgentName = "Groups"; msg.message = string.Format("You have been added to the group."); msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageBox; msg.fromGroup = false; msg.offline = (byte)0; // don't bother storing this one to fetch on login if user offline msg.ParentEstateID = 0; msg.Position = Vector3.Zero; msg.RegionID = UUID.Zero.Guid; msg.binaryBucket = new byte[0]; OutgoingInstantMessage(msg, inviteInfo.AgentID); UpdateAllClientsWithGroupInfo(inviteInfo.AgentID); // TODO: If the inviter is still online, they need an agent dataupdate // and maybe group membership updates for the invitee m_groupData.RemoveAgentToGroupInvite(GetClientGroupRequestID(remoteClient), inviteID); } // Reject if (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received a reject invite notice."); m_groupData.RemoveAgentToGroupInvite(GetClientGroupRequestID(remoteClient), inviteID); } } } // Group notices if ((im.dialog == (byte)InstantMessageDialog.GroupNotice)) { if (!m_groupNoticesEnabled) { return; } if (im.offline != 0) { // Delivery of stored (offline) IMs is handled by the caller return; } UUID GroupID = new UUID(im.toAgentID); GroupRecord group = m_groupData.GetGroupRecord(GetClientGroupRequestID(remoteClient), GroupID, null); if (group == null) { m_log.ErrorFormat("[GROUPS]: Failed to find notice group {0}", GroupID); return; } UUID NoticeID = UUID.Random(); string Subject = im.message.Substring(0, im.message.IndexOf('|')); string Message = im.message.Substring(Subject.Length + 1); byte[] bucket; if ((im.binaryBucket.Length == 1) && (im.binaryBucket[0] == 0)) { // Sending a notice without an attachment bucket = new byte[19]; bucket[0] = 0; // HasAttachment boolean bucket[1] = 0; // attachment type GroupID.ToBytes(bucket, 2); bucket[18] = 0; // attachment name } else { // Sending a notice with an attachment string binBucket = OpenMetaverse.Utils.BytesToString(im.binaryBucket); binBucket = binBucket.Remove(0, 14).Trim(); OSDMap binBucketOSD = (OSDMap)OSDParser.DeserializeLLSDXml(binBucket); UUID itemId = binBucketOSD["item_id"].AsUUID(); UUID ownerID = binBucketOSD["owner_id"].AsUUID(); if (ownerID != remoteClient.AgentId) { m_log.ErrorFormat("[GROUPS]: Notice attachment in group {0} not owned {1} by the user sending it {2}", GroupID.ToString(), ownerID.ToString(), remoteClient.AgentId); return; } CachedUserInfo ownerUserInfo = m_sceneList[0].CommsManager.UserService.GetUserDetails(ownerID); if (ownerUserInfo == null) { m_log.ErrorFormat("[GROUPS]: Failed to find notice sender {0} for item {1}", ownerID, itemId); return; } InventoryItemBase item = ownerUserInfo.FindItem(itemId); if (item == null) { m_log.ErrorFormat("[GROUPS]: Item {0} not found for notice sender {1}", itemId, ownerID); return; } if (!m_sceneList[0].Permissions.BypassPermissions()) { if (((item.CurrentPermissions & (uint)PermissionMask.Transfer) == 0) || ((item.CurrentPermissions & (uint)PermissionMask.Copy) == 0)) { remoteClient.SendAgentAlertMessage("Notice attachments must be copyable and transferable.", false); return; } } // Clone the item for storage as a group-owned item in the db that is NOT in any user's inventory. InventoryItemBase groupItem = AddToGroupInventory(remoteClient, group, item); // format for database storage bucket = FormatBucketForStorage(true, (byte)groupItem.AssetType, GroupID, groupItem.ID, NoticeID, item.Name); } if (m_groupData.AddGroupNotice(GetClientGroupRequestID(remoteClient), GroupID, NoticeID, im.fromAgentName, Subject, Message, bucket)) { // Once added to the DB above with owner ID, replace that with the GroupID that the viewers need GroupID.ToBytes(bucket, 2); // 16-byte UUID if (OnNewGroupNotice != null) { OnNewGroupNotice(GroupID, NoticeID); } // Get the list of users who have this sender muted. List<UUID> muters; m_muteListModule = m_sceneList[0].RequestModuleInterface<IMuteListModule>(); if (m_muteListModule != null) muters = m_muteListModule.GetInverseMuteList(remoteClient.AgentId); else muters = new List<UUID>(); // Send notice out to everyone that wants notices foreach (GroupMembersData member in m_groupData.GetGroupMembers(GetClientGroupRequestID(remoteClient), GroupID, false)) { if (member.AcceptNotices && !muters.Contains(member.AgentID)) { SendGroupNoticeIM(NoticeID, member.AgentID, OpenMetaverse.InstantMessageDialog.GroupNotice, false); } } } } if (im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryDeclined) { if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: Declined - removing session {0}", im.imSessionID); lock (GroupAttachmentCache) { GroupAttachmentCache.Remove(im.imSessionID); } } if (im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryAccepted) { UUID NoticeID = UUID.Zero; lock (GroupAttachmentCache) { if (GroupAttachmentCache.ContainsKey(im.imSessionID)) { // Retrieve the notice ID and remove it from the cache. NoticeID = GroupAttachmentCache[im.imSessionID]; GroupAttachmentCache.Remove(im.imSessionID); } } if (NoticeID == UUID.Zero) { m_log.ErrorFormat("[GROUPS]: Accepted attachment for session {0} - NOT FOUND", im.imSessionID); remoteClient.SendAgentAlertMessage("Attachment not saved - specified group notice not found.", false); return; } if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: Accepted attachment for session {0} notice {1}", im.imSessionID, NoticeID); GroupNoticeInfo notice = m_groupData.GetGroupNotice(GetClientGroupRequestID(remoteClient), NoticeID); if (notice == null) { remoteClient.SendAgentAlertMessage("Could not find the required group notice.", false); return; } InitializeNoticeFromBucket(notice); UUID groupId = notice.GroupID; UUID itemId = notice.noticeData.ItemID; // we need a userInfo structure to get the sessionID to use in case the inventory service needs a secure service connection CachedUserInfo userInfo = m_sceneList[0].CommsManager.UserService.GetUserDetails(remoteClient.AgentId); if (userInfo == null) { m_log.ErrorFormat("[GROUPS]: Failed to find notice recipient {0} for item {1}", remoteClient.AgentId, itemId); remoteClient.SendAgentAlertMessage("Attachment not saved - user profile not found.", false); return; } InventoryItemBase groupItem = FetchGroupItem(groupId, itemId); if (groupItem == null) // For now support fallback to the legacy inventory system. groupItem = FindLegacyInventoryItem(groupId, itemId); if (groupItem != null) { InventoryItemBase deliveredItem = DeliverGroupInventory(remoteClient, groupId, groupItem); if (deliveredItem != null) { remoteClient.SendInventoryItemCreateUpdate(deliveredItem, 0); remoteClient.SendAgentAlertMessage("Group notice attachment '"+groupItem.Name+"' saved to inventory.", false); } else { remoteClient.SendAgentAlertMessage("Attachment not saved - delivery failed.", false); m_log.ErrorFormat("[GROUPS]: Failed to deliver notice attachment {0} for {1}", itemId, remoteClient.AgentId); } } else { remoteClient.SendAgentAlertMessage("Attachment not saved - item not found.", false); m_log.ErrorFormat("[GROUPS]: Missing notice attachment {0} for {1}", itemId, remoteClient.AgentId); } if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: Accepted and completed for session {0}", im.imSessionID); } // Interop, received special 210 code for ejecting a group member // this only works within the comms servers domain, and won't work hypergrid // TODO:FIXME: Use a presense server of some kind to find out where the // client actually is, and try contacting that region directly to notify them, // or provide the notification via xmlrpc update queue if ((im.dialog == 210)) { // This is sent from the region that the ejectee was ejected from // if it's being delivered here, then the ejectee is here // so we need to send local updates to the agent. UUID ejecteeID = new UUID(im.toAgentID); im.dialog = (byte)InstantMessageDialog.MessageFromAgent; OutgoingInstantMessage(im, ejecteeID); IClientAPI ejectee = GetActiveClient(ejecteeID); if (ejectee != null) { UUID groupID = new UUID(im.fromAgentID); ejectee.SendAgentDropGroup(groupID); } } }
private void SendGroupNoticeIM(UUID NoticeID, UUID AgentID, OpenMetaverse.InstantMessageDialog dialog, bool checkMuted) { // Build notice IIM GridInstantMessage msg = CreateGroupNoticeIM(UUID.Zero, NoticeID, (byte)dialog); if (msg == null) return; // old bad one stored from offlines? UUID sender = new UUID(msg.fromAgentID); if (checkMuted) { m_muteListModule = m_sceneList[0].RequestModuleInterface<IMuteListModule>(); if (m_muteListModule != null) if (m_muteListModule.IsMuted(sender, AgentID)) return; } bool HasAttachment = (msg.binaryBucket[0] != 0); if (HasAttachment) // save the notice with the session ID { lock (GroupAttachmentCache) { GroupAttachmentCache[msg.imSessionID] = NoticeID; } } msg.toAgentID = AgentID.Guid; IClientAPI localClient = GetActiveClient(AgentID); if (localClient != null) { if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: Recipient ({0}) is local, delivering group notice directly", localClient.Name); localClient.SendInstantMessage(msg); } else { // send for offline storage if (HasAttachment) // clean up the cache item added above { lock (GroupAttachmentCache) { GroupAttachmentCache.Remove(msg.imSessionID); } } // need to reformat this with db storage bucket, not the viewer IM bucket // format for database storage int bucketLen = msg.binaryBucket.Length; byte[] OfflineBucket = new byte[bucketLen + 4 + 16]; OfflineBucket[0] = 0xFF; // sign, would be 00 or 01 here in a normal viewer IM bucket OfflineBucket[1] = 0x00; OfflineBucket[2] = 0x00; OfflineBucket[3] = 0x00; // Spare bytes NoticeID.ToBytes(OfflineBucket, 4); // 16-byte UUID msg.binaryBucket.CopyTo(OfflineBucket, 20); msg.binaryBucket = OfflineBucket; if (m_debugEnabled) m_log.InfoFormat("[GROUPS]: Recipient ({0}) is not local, delivering group notice via TransferModule", msg.toAgentID); m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Message Sent: {0}", success ? "Succeeded" : "Failed"); }); } }
private byte[] CreateBitBucketForGroupAttachment(GroupNoticeData groupNoticeData, UUID groupID) { int i = 20; i += groupNoticeData.ItemName.Length; byte[] bitbucket = new byte[i]; groupID.ToBytes(bitbucket, 2); byte[] name = Utils.StringToBytes(" " + groupNoticeData.ItemName); Array.ConstrainedCopy(name, 0, bitbucket, 18, name.Length); //Utils.Int16ToBytes((short)item.AssetType, bitbucket, 0); bitbucket[0] = 1; // 0 for no attachment, 1 for attachment bitbucket[1] = (byte)groupNoticeData.AssetType; // Asset type return bitbucket; }
private void AddToCompiledWL(UUID v, ref byte[] mBlock, ref int pos) { v.ToBytes(mBlock, pos); pos += 16; }
private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name); // Group invitations if ((im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) || (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline)) { UUID inviteID = new UUID(im.imSessionID); GroupInviteInfo inviteInfo = m_groupData.GetAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID); if (inviteInfo == null) { if (m_debugEnabled) m_log.WarnFormat("[GROUPS]: Received an Invite IM for an invite that does not exist {0}.", inviteID); return; } if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Invite is for Agent {0} to Group {1}.", inviteInfo.AgentID, inviteInfo.GroupID); UUID fromAgentID = new UUID(im.fromAgentID); if ((inviteInfo != null) && (fromAgentID == inviteInfo.AgentID)) { // Accept if (im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received an accept invite notice."); // and the sessionid is the role m_groupData.AddAgentToGroup(GetRequestingAgentID(remoteClient), inviteInfo.AgentID, inviteInfo.GroupID, inviteInfo.RoleID); GridInstantMessage msg = new GridInstantMessage(); msg.imSessionID = UUID.Zero.Guid; msg.fromAgentID = UUID.Zero.Guid; msg.toAgentID = inviteInfo.AgentID.Guid; msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); msg.fromAgentName = "Groups"; msg.message = string.Format("You have been added to the group."); msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageBox; msg.fromGroup = false; msg.offline = (byte)0; msg.ParentEstateID = 0; msg.Position = Vector3.Zero; msg.RegionID = UUID.Zero.Guid; msg.binaryBucket = new byte[0]; OutgoingInstantMessage(msg, inviteInfo.AgentID); UpdateAllClientsWithGroupInfo(inviteInfo.AgentID); // TODO: If the inviter is still online, they need an agent dataupdate // and maybe group membership updates for the invitee m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID); } // Reject if (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received a reject invite notice."); m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID); } } } // Group notices if ((im.dialog == (byte)InstantMessageDialog.GroupNotice)) { if (!m_groupNoticesEnabled) { return; } UUID GroupID = new UUID(im.toAgentID); if (m_groupData.GetGroupRecord(GetRequestingAgentID(remoteClient), GroupID, null) != null) { UUID NoticeID = UUID.Random(); string Subject = im.message.Substring(0, im.message.IndexOf('|')); string Message = im.message.Substring(Subject.Length + 1); byte[] bucket; if ((im.binaryBucket.Length == 1) && (im.binaryBucket[0] == 0)) { bucket = new byte[19]; bucket[0] = 0; //dunno bucket[1] = 0; //dunno GroupID.ToBytes(bucket, 2); bucket[18] = 0; //dunno } else { string binBucket = OpenMetaverse.Utils.BytesToString(im.binaryBucket); binBucket = binBucket.Remove(0, 14).Trim(); if (m_debugEnabled) { m_log.WarnFormat("I don't understand a group notice binary bucket of: {0}", binBucket); OSDMap binBucketOSD = (OSDMap)OSDParser.DeserializeLLSDXml(binBucket); foreach (string key in binBucketOSD.Keys) { if (binBucketOSD.ContainsKey(key)) { m_log.WarnFormat("{0}: {1}", key, binBucketOSD[key].ToString()); } } } // treat as if no attachment bucket = new byte[19]; bucket[0] = 0; //dunno bucket[1] = 0; //dunno GroupID.ToBytes(bucket, 2); bucket[18] = 0; //dunno } m_groupData.AddGroupNotice(GetRequestingAgentID(remoteClient), GroupID, NoticeID, im.fromAgentName, Subject, Message, bucket); if (OnNewGroupNotice != null) { OnNewGroupNotice(GroupID, NoticeID); } // Send notice out to everyone that wants notices foreach (GroupMembersData member in m_groupData.GetGroupMembers(GetRequestingAgentID(remoteClient), GroupID)) { if (m_debugEnabled) { UserAccount targetUser = m_sceneList[0].UserAccountService.GetUserAccount(remoteClient.Scene.RegionInfo.ScopeID, member.AgentID); if (targetUser != null) { m_log.DebugFormat("[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", NoticeID, targetUser.FirstName + " " + targetUser.LastName, member.AcceptNotices); } else { m_log.DebugFormat("[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", NoticeID, member.AgentID, member.AcceptNotices); } } if (member.AcceptNotices) { // Build notice IIM GridInstantMessage msg = CreateGroupNoticeIM(UUID.Zero, NoticeID, (byte)OpenMetaverse.InstantMessageDialog.GroupNotice); msg.toAgentID = member.AgentID.Guid; OutgoingInstantMessage(msg, member.AgentID); } } } } // Interop, received special 210 code for ejecting a group member // this only works within the comms servers domain, and won't work hypergrid // TODO:FIXME: Use a presense server of some kind to find out where the // client actually is, and try contacting that region directly to notify them, // or provide the notification via xmlrpc update queue if ((im.dialog == 210)) { // This is sent from the region that the ejectee was ejected from // if it's being delivered here, then the ejectee is here // so we need to send local updates to the agent. UUID ejecteeID = new UUID(im.toAgentID); im.dialog = (byte)InstantMessageDialog.MessageFromAgent; OutgoingInstantMessage(im, ejecteeID); IClientAPI ejectee = GetActiveClient(ejecteeID); if (ejectee != null) { UUID groupID = new UUID(im.imSessionID); ejectee.SendAgentDropGroup(groupID); } } }
private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im) { if (m_debugEnabled) m_log.DebugFormat( "[GROUPS]: {0} called for {1}, message type {2}", System.Reflection.MethodBase.GetCurrentMethod().Name, remoteClient.Name, (InstantMessageDialog)im.dialog); // Group invitations if ((im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) || (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline)) { UUID inviteID = new UUID(im.imSessionID); GroupInviteInfo inviteInfo = m_groupData.GetAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID); if (inviteInfo == null) { if (m_debugEnabled) m_log.WarnFormat("[GROUPS]: Received an Invite IM for an invite that does not exist {0}.", inviteID); return; } if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Invite is for Agent {0} to Group {1}.", inviteInfo.AgentID, inviteInfo.GroupID); UUID fromAgentID = new UUID(im.fromAgentID); if ((inviteInfo != null) && (fromAgentID == inviteInfo.AgentID)) { // Accept if (im.dialog == (byte)InstantMessageDialog.GroupInvitationAccept) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received an accept invite notice."); // and the sessionid is the role m_groupData.AddAgentToGroup(GetRequestingAgentID(remoteClient), inviteInfo.AgentID, inviteInfo.GroupID, inviteInfo.RoleID); GridInstantMessage msg = new GridInstantMessage(); msg.imSessionID = UUID.Zero.Guid; msg.fromAgentID = UUID.Zero.Guid; msg.toAgentID = inviteInfo.AgentID.Guid; msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); msg.fromAgentName = "Groups"; msg.message = string.Format("You have been added to the group."); msg.dialog = (byte)OpenMetaverse.InstantMessageDialog.MessageBox; msg.fromGroup = false; msg.offline = (byte)0; msg.ParentEstateID = 0; msg.Position = Vector3.Zero; msg.RegionID = UUID.Zero.Guid; msg.binaryBucket = new byte[0]; OutgoingInstantMessage(msg, inviteInfo.AgentID); IClientAPI client = GetActiveClient(inviteInfo.AgentID); if (client != null) SendDataUpdate(remoteClient, true); // TODO: If the inviter is still online, they need an agent dataupdate // and maybe group membership updates for the invitee m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID); } // Reject if (im.dialog == (byte)InstantMessageDialog.GroupInvitationDecline) { if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Received a reject invite notice."); m_groupData.RemoveAgentToGroupInvite(GetRequestingAgentID(remoteClient), inviteID); } } } // Group notices if ((im.dialog == (byte)InstantMessageDialog.GroupNotice)) { if (!m_groupNoticesEnabled) { return; } UUID GroupID = new UUID(im.toAgentID); if (m_groupData.GetGroupRecord(GetRequestingAgentID(remoteClient), GroupID, null) != null) { UUID NoticeID = UUID.Random(); string Subject = im.message.Substring(0, im.message.IndexOf('|')); string Message = im.message.Substring(Subject.Length + 1); InventoryItemBase item = null; bool hasAttachment = false; UUID itemID = UUID.Zero; //Assignment to quiet compiler UUID ownerID = UUID.Zero; //Assignment to quiet compiler byte[] bucket; if (im.binaryBucket.Length >= 1 && im.binaryBucket[0] > 0) { string binBucket = OpenMetaverse.Utils.BytesToString(im.binaryBucket); binBucket = binBucket.Remove(0, 14).Trim(); OSDMap binBucketOSD = (OSDMap)OSDParser.DeserializeLLSDXml(binBucket); if (binBucketOSD is OSD) { OSDMap binBucketMap = (OSDMap)binBucketOSD; itemID = binBucketMap["item_id"].AsUUID(); ownerID = binBucketMap["owner_id"].AsUUID(); //Attempt to get the details of the attached item. //If sender doesn't own the attachment, the item //variable will be set to null and attachment will //not be included with the group notice. Scene scene = (Scene)remoteClient.Scene; item = new InventoryItemBase(itemID, ownerID); item = scene.InventoryService.GetItem(item); if (item != null) { //Got item details so include the attachment. hasAttachment = true; } } else { m_log.DebugFormat("[Groups]: Received OSD with unexpected type: {0}", binBucketOSD.GetType()); } } if (hasAttachment) { //Bucket contains information about attachment. // //Byte offset and description of bucket data: //0: 1 byte indicating if attachment is present //1: 1 byte indicating the type of attachment //2: 16 bytes - Group UUID //18: 16 bytes - UUID of the attachment owner //34: 16 bytes - UUID of the attachment //50: variable - Name of the attachment //??: NUL byte to terminate the attachment name byte[] name = Encoding.UTF8.GetBytes(item.Name); bucket = new byte[51 + name.Length];//3 bytes, 3 UUIDs, and name bucket[0] = 1; //Has attachment flag bucket[1] = (byte)item.InvType; //Type of Attachment GroupID.ToBytes(bucket, 2); ownerID.ToBytes(bucket, 18); itemID.ToBytes(bucket, 34); name.CopyTo(bucket, 50); } else { bucket = new byte[19]; bucket[0] = 0; //Has attachment flag bucket[1] = 0; //Type of attachment GroupID.ToBytes(bucket, 2); bucket[18] = 0; //NUL terminate name of attachment } m_groupData.AddGroupNotice(GetRequestingAgentID(remoteClient), GroupID, NoticeID, im.fromAgentName, Subject, Message, bucket); if (OnNewGroupNotice != null) { OnNewGroupNotice(GroupID, NoticeID); } if (m_debugEnabled) { foreach (GroupMembersData member in m_groupData.GetGroupMembers(GetRequestingAgentID(remoteClient), GroupID)) { if (m_debugEnabled) { UserAccount targetUser = m_sceneList[0].UserAccountService.GetUserAccount( remoteClient.Scene.RegionInfo.ScopeID, member.AgentID); if (targetUser != null) { m_log.DebugFormat( "[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", NoticeID, targetUser.FirstName + " " + targetUser.LastName, member.AcceptNotices); } else { m_log.DebugFormat( "[GROUPS]: Prepping group notice {0} for agent: {1} who Accepts Notices ({2})", NoticeID, member.AgentID, member.AcceptNotices); } } } } GridInstantMessage msg = CreateGroupNoticeIM(UUID.Zero, NoticeID, (byte)OpenMetaverse.InstantMessageDialog.GroupNotice); if (m_groupsMessagingModule != null) m_groupsMessagingModule.SendMessageToGroup( msg, GroupID, remoteClient.AgentId, gmd => gmd.AcceptNotices); } } if (im.dialog == (byte)InstantMessageDialog.GroupNoticeInventoryAccepted) { //Is bucket large enough to hold UUID of the attachment? if (im.binaryBucket.Length < 16) return; UUID noticeID = new UUID(im.imSessionID); if (m_debugEnabled) m_log.DebugFormat("[GROUPS]: Requesting notice {0} for {1}", noticeID, remoteClient.AgentId); GroupNoticeInfo notice = m_groupData.GetGroupNotice(GetRequestingAgentID(remoteClient), noticeID); if (notice != null) { UUID giver = new UUID(notice.BinaryBucket, 18); UUID attachmentUUID = new UUID(notice.BinaryBucket, 34); if (m_debugEnabled) m_log.DebugFormat("[Groups]: Giving inventory from {0} to {1}", giver, remoteClient.AgentId); string message; InventoryItemBase itemCopy = ((Scene)(remoteClient.Scene)).GiveInventoryItem(remoteClient.AgentId, giver, attachmentUUID, out message); if (itemCopy == null) { remoteClient.SendAgentAlertMessage(message, false); return; } remoteClient.SendInventoryItemCreateUpdate(itemCopy, 0); } else { if (m_debugEnabled) m_log.DebugFormat( "[GROUPS]: Could not find notice {0} for {1} on GroupNoticeInventoryAccepted.", noticeID, remoteClient.AgentId); } } // Interop, received special 210 code for ejecting a group member // this only works within the comms servers domain, and won't work hypergrid // TODO:FIXME: Use a presence server of some kind to find out where the // client actually is, and try contacting that region directly to notify them, // or provide the notification via xmlrpc update queue if ((im.dialog == 210)) { // This is sent from the region that the ejectee was ejected from // if it's being delivered here, then the ejectee is here // so we need to send local updates to the agent. UUID ejecteeID = new UUID(im.toAgentID); im.dialog = (byte)InstantMessageDialog.MessageFromAgent; OutgoingInstantMessage(im, ejecteeID); IClientAPI ejectee = GetActiveClient(ejecteeID); if (ejectee != null) { UUID groupID = new UUID(im.imSessionID); ejectee.SendAgentDropGroup(groupID); } } }