/* * Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2021-2025, 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 OpenMetaverse.StructuredData; using MessagePack; namespace OpenMetaverse { /// /// Base Class for Inventory Items /// [MessagePackObject] [Union(0, typeof(InventoryFolder))] [Union(1, typeof(InventoryItem))] [Union(2, typeof(InventoryAnimation))] [Union(3, typeof(InventoryAttachment))] [Union(4, typeof(InventoryCallingCard))] [Union(5, typeof(InventoryCategory))] [Union(6, typeof(InventoryGesture))] [Union(7, typeof(InventoryLSL))] [Union(8, typeof(InventoryLandmark))] [Union(9, typeof(InventoryMaterial))] [Union(10, typeof(InventoryNotecard))] [Union(11, typeof(InventoryObject))] [Union(12, typeof(InventorySettings))] [Union(13, typeof(InventorySnapshot))] [Union(14, typeof(InventorySound))] [Union(15, typeof(InventoryTexture))] [Union(16, typeof(InventoryWearable))] public abstract partial class InventoryBase { /// of item/folder [Key("UUID")] public UUID UUID; /// of parent folder [Key("ParentUUID")] public UUID ParentUUID; /// Name of item/folder [Key("Name")] public string Name; /// Item/Folder Owners [Key("OwnerID")] public UUID OwnerID; /// /// Constructor, takes an itemID as a parameter /// /// The of the item protected InventoryBase(UUID UUID) { if (UUID == UUID.Zero) Logger.Log("Initializing an InventoryBase with UUID.Zero", Helpers.LogLevel.Warning); this.UUID = UUID; } /// /// Generates a number corresponding to the value of the object to support the use of a hash table, /// suitable for use in hashing algorithms and data structures such as a hash table /// /// A Hashcode of all the combined InventoryBase fields public override int GetHashCode() { return UUID.GetHashCode() ^ ParentUUID.GetHashCode() ^ Name.GetHashCode() ^ OwnerID.GetHashCode(); } /// /// Determine whether the specified object is equal to the current object /// /// InventoryBase object to compare against /// true if objects are the same public override bool Equals(object obj) { return obj is InventoryBase inv && Equals(inv); } /// /// Determine whether the specified object is equal to the current object /// /// InventoryBase object to compare against /// true if objects are the same public virtual bool Equals(InventoryBase o) { return o.UUID == UUID && o.ParentUUID == ParentUUID && o.Name == Name && o.OwnerID == OwnerID; } /// /// Convert inventory to OSD /// /// OSD representation public abstract OSD GetOSD(); } /// /// An Item in Inventory /// [MessagePackObject] public partial class InventoryItem : InventoryBase { public override string ToString() { return $"{AssetType} {AssetUUID} ({InventoryType} {UUID}) '{Name}'/'{Description}' {Permissions}"; } /// of the underlying asset [Key("AssetUUID")] public UUID AssetUUID; /// Combined of the item [Key("Permissions")] public Permissions Permissions; /// of the underlying asset [Key("AssetType")] public AssetType AssetType; /// of the item [Key("InventoryType")] public InventoryType InventoryType; /// of the creator of the item [Key("CreatorID")] public UUID CreatorID; /// Description of the item [Key("Description")] public string Description; /// s the item is owned by [Key("GroupID")] public UUID GroupID; /// If true, item is owned by a group [Key("GroupOwned")] public bool GroupOwned; /// Price the item can be purchased for [Key("SalePrice")] public int SalePrice; /// of the item [Key("SaleType")] public SaleType SaleType; /// Combined flags from [Key("Flags")] public uint Flags; /// Time and date the inventory item was created, stored as /// UTC (Coordinated Universal Time) [Key("CreationDate")] public DateTime CreationDate; /// Used to update the AssetID in requests sent to the server [Key("TransactionID")] public UUID TransactionID; /// of the previous owner of the item [Key("LastOwnerID")] public UUID LastOwnerID; /// inventoryID that this item points to, else this item's inventoryID [IgnoreMember] public UUID ActualUUID => IsLink() ? AssetUUID : UUID; /// /// Construct a new InventoryItem object /// /// The of the item public InventoryItem(UUID UUID) : base(UUID) { } /// /// Construct a new InventoryItem object of a specific Type /// /// The type of item from /// of the item public InventoryItem(InventoryType type, UUID itemID) : base(itemID) { InventoryType = type; } /// /// Indicates inventory item is a link /// /// True if inventory item is a link to another inventory item public bool IsLink() { return AssetType == AssetType.Link || AssetType == AssetType.LinkFolder; } /// /// Generates a number corresponding to the value of the object to support the use of a hash table. /// Suitable for use in hashing algorithms and data structures such as a hash table /// /// A Hashcode of all the combined InventoryItem fields public override int GetHashCode() { return AssetUUID.GetHashCode() ^ Permissions.GetHashCode() ^ AssetType.GetHashCode() ^ InventoryType.GetHashCode() ^ Description.GetHashCode() ^ GroupID.GetHashCode() ^ GroupOwned.GetHashCode() ^ SalePrice.GetHashCode() ^ SaleType.GetHashCode() ^ Flags.GetHashCode() ^ CreationDate.GetHashCode() ^ LastOwnerID.GetHashCode(); } /// /// /// Compares an object /// /// The object to compare /// true if comparison object matches public override bool Equals(object obj) { return obj is InventoryItem item && Equals(item); } /// /// /// Determine whether the specified object is equal to the current object /// /// The object to compare against /// true if objects are the same public override bool Equals(InventoryBase o) { return o is InventoryItem item && Equals(item); } /// /// Determine whether the specified object is equal to the current object /// /// The object to compare against /// true if objects are the same public bool Equals(InventoryItem o) { return base.Equals(o) && o.AssetType == AssetType && o.AssetUUID == AssetUUID && o.CreationDate == CreationDate && o.Description == Description && o.Flags == Flags && o.GroupID == GroupID && o.GroupOwned == GroupOwned && o.InventoryType == InventoryType && o.Permissions.Equals(Permissions) && o.SalePrice == SalePrice && o.SaleType == SaleType && o.LastOwnerID == LastOwnerID; } /// /// Create InventoryItem from OSD /// /// OSD Data that makes up InventoryItem /// Inventory item created public static InventoryItem FromOSD(OSD data) { OSDMap descItem = (OSDMap)data; /* * Objects that have been attached in-world prior to being stored on the * asset server are stored with the InventoryType of 0 (Texture) * instead of 17 (Attachment) * * This corrects that behavior by forcing Object Asset types that have an * invalid InventoryType with the proper InventoryType of Attachment. */ InventoryType type = (InventoryType)descItem["inv_type"].AsInteger(); if (type == InventoryType.Texture && ((AssetType)descItem["type"].AsInteger() == AssetType.Object || (AssetType)descItem["type"].AsInteger() == AssetType.Mesh)) { type = InventoryType.Attachment; } InventoryItem item = InventoryManager.CreateInventoryItem(type, descItem["item_id"]); item.ParentUUID = descItem["parent_id"]; item.Name = descItem["name"]; item.Description = descItem["desc"]; item.OwnerID = descItem["agent_id"]; item.ParentUUID = descItem["parent_id"]; item.AssetUUID = descItem["asset_id"]; item.AssetType = (AssetType)descItem["type"].AsInteger(); item.CreationDate = Utils.UnixTimeToDateTime(descItem["created_at"]); item.Flags = descItem["flags"]; OSDMap perms = (OSDMap)descItem["permissions"]; item.CreatorID = perms["creator_id"]; item.LastOwnerID = perms["last_owner_id"]; item.Permissions = new Permissions(perms["base_mask"], perms["everyone_mask"], perms["group_mask"], perms["next_owner_mask"], perms["owner_mask"]); item.GroupOwned = perms["is_owner_group"]; item.GroupID = perms["group_id"]; OSDMap sale = (OSDMap)descItem["sale_info"]; item.SalePrice = sale["sale_price"]; item.SaleType = (SaleType)sale["sale_type"].AsInteger(); return item; } /// /// Update InventoryItem from new OSD data /// /// Data to update in format public void Update(OSDMap data) { if (data.ContainsKey("item_id")) { UUID = data["item_id"].AsUUID(); } if (data.ContainsKey("parent_id")) { ParentUUID = data["parent_id"].AsUUID(); } if (data.ContainsKey("agent_id")) { OwnerID = data["agent_id"].AsUUID(); } if (data.ContainsKey("name")) { Name = data["name"].AsString(); } if (data.ContainsKey("desc")) { Description = data["desc"].AsString(); } if (data.TryGetValue("permissions", out var permissions)) { Permissions = Permissions.FromOSD(permissions); } if (data.TryGetValue("sale_info", out var saleInfo)) { OSDMap sale = (OSDMap)saleInfo; SalePrice = sale["sale_price"].AsInteger(); SaleType = (SaleType)sale["sale_type"].AsInteger(); } if (data.ContainsKey("shadow_id")) { AssetUUID = InventoryManager.DecryptShadowID(data["shadow_id"].AsUUID()); } if (data.ContainsKey("asset_id")) { AssetUUID = data["asset_id"].AsUUID(); } if (data.ContainsKey("linked_id")) { AssetUUID = data["linked_id"].AsUUID(); } if (data.ContainsKey("type")) { AssetType type = AssetType.Unknown; switch (data["type"].Type) { case OSDType.String: type = Utils.StringToAssetType(data["type"].AsString()); break; case OSDType.Integer: type = (AssetType)data["type"].AsInteger(); break; } if (type != AssetType.Unknown) { AssetType = type; } } if (data.ContainsKey("inv_type")) { InventoryType type = InventoryType.Unknown; switch (data["inv_type"].Type) { case OSDType.String: type = Utils.StringToInventoryType(data["inv_type"].AsString()); break; case OSDType.Integer: type = (InventoryType)data["inv_type"].AsInteger(); break; } if (type != InventoryType.Unknown) { InventoryType = type; } } if (data.TryGetValue("flags", out var flags)) { Flags = flags; } if (data.TryGetValue("created_at", out var createdAt)) { CreationDate = Utils.UnixTimeToDateTime(createdAt); } } /// /// Convert InventoryItem to OSD /// /// OSD representation of InventoryItem public override OSD GetOSD() { OSDMap map = new OSDMap { ["item_id"] = UUID, ["parent_id"] = ParentUUID, ["type"] = (sbyte)AssetType, ["inv_type"] = (sbyte)InventoryType, ["flags"] = Flags, ["name"] = Name, ["desc"] = Description, ["asset_id"] = AssetUUID, ["created_at"] = CreationDate }; OSDMap perms = (OSDMap)Permissions.GetOSD(); perms["creator_id"] = CreatorID; perms["last_owner_id"] = LastOwnerID; perms["is_owner_group"] = GroupOwned; perms["group_id"] = GroupID; map["permissions"] = perms; OSDMap sale = new OSDMap { ["sale_price"] = SalePrice, ["sale_type"] = (sbyte)SaleType }; map["sale_info"] = sale; return map; } } /// /// /// InventoryTexture Class representing a graphical image /// /// [MessagePackObject] public partial class InventoryTexture : InventoryItem { /// /// Construct an InventoryTexture object /// /// A which becomes the /// objects UUID public InventoryTexture(UUID UUID) : base(UUID) { InventoryType = InventoryType.Texture; } } /// /// /// InventorySound Class representing a playable sound /// [MessagePackObject] public partial class InventorySound : InventoryItem { /// /// Construct an InventorySound object /// /// A which becomes the /// objects UUID public InventorySound(UUID UUID) : base(UUID) { InventoryType = InventoryType.Sound; } } /// /// /// InventoryCallingCard Class, contains information on another avatar /// [MessagePackObject] public partial class InventoryCallingCard : InventoryItem { /// /// Construct an InventoryCallingCard object /// /// A which becomes the /// objects UUID public InventoryCallingCard(UUID UUID) : base(UUID) { InventoryType = InventoryType.CallingCard; } } /// /// /// InventoryLandmark Class, contains details on a specific location /// [MessagePackObject] public partial class InventoryLandmark : InventoryItem { /// /// Construct an InventoryLandmark object /// /// A which becomes the /// objects UUID public InventoryLandmark(UUID UUID) : base(UUID) { InventoryType = InventoryType.Landmark; } /// /// Landmarks use the InventoryItemFlags struct and will have a flag of 1 set if they have been visited /// [IgnoreMember] public bool LandmarkVisited { get => (Flags & 1) != 0; set { if (value) Flags |= 1; else Flags &= ~1u; } } } /// /// /// InventoryObject Class contains details on a primitive or coalesced set of primitives /// [MessagePackObject] public partial class InventoryObject : InventoryItem { /// /// Construct an InventoryObject object /// /// A which becomes the /// objects UUID public InventoryObject(UUID UUID) : base(UUID) { InventoryType = InventoryType.Object; } /// /// Gets or sets the upper byte of the Flags value /// [IgnoreMember] public InventoryItemFlags ItemFlags { get => (InventoryItemFlags)(Flags & ~0xFF); set => Flags = (uint)value | (Flags & 0xFF); } /// /// Gets or sets the object attachment point, the lower byte of the Flags value /// [IgnoreMember] public AttachmentPoint AttachPoint { get => (AttachmentPoint)(Flags & 0xFF); set => Flags = (uint)value | (Flags & 0xFFFFFF00); } } /// /// /// InventoryNotecard Class, contains details on an encoded text document /// [MessagePackObject] public partial class InventoryNotecard : InventoryItem { /// /// Construct an InventoryNotecard object /// /// A which becomes the /// objects UUID public InventoryNotecard(UUID UUID) : base(UUID) { InventoryType = InventoryType.Notecard; } } /// /// /// InventoryCategory Class /// [MessagePackObject] public partial class InventoryCategory : InventoryItem { /// /// Construct an InventoryCategory object /// /// A which becomes the /// objects UUID public InventoryCategory(UUID UUID) : base(UUID) { InventoryType = InventoryType.Category; } } /// /// /// InventoryLSL Class, represents a Linden Scripting Language object /// [MessagePackObject] public partial class InventoryLSL : InventoryItem { /// /// Construct an InventoryLSL object /// /// A which becomes the /// objects UUID public InventoryLSL(UUID UUID) : base(UUID) { InventoryType = InventoryType.LSL; } } /// /// /// InventorySnapshot Class, an image taken with the viewer /// [MessagePackObject] public partial class InventorySnapshot : InventoryItem { /// /// /// Construct an InventorySnapshot object /// /// A which becomes the /// objects UUID public InventorySnapshot(UUID UUID) : base(UUID) { InventoryType = InventoryType.Snapshot; } } /// /// InventoryAttachment Class, contains details on an attachable object /// [MessagePackObject] public partial class InventoryAttachment : InventoryItem { /// /// Construct an InventoryAttachment object /// /// A which becomes the /// objects UUID public InventoryAttachment(UUID UUID) : base(UUID) { InventoryType = InventoryType.Attachment; } /// /// Get the last AttachmentPoint this object was attached to /// [IgnoreMember] public AttachmentPoint AttachmentPoint { get => (AttachmentPoint)Flags; set => Flags = (uint)value; } } /// /// /// InventoryWearable Class, details on a clothing item or body part /// [MessagePackObject] public partial class InventoryWearable : InventoryItem { /// /// Construct an InventoryWearable object /// /// A which becomes the /// objects UUID public InventoryWearable(UUID UUID) : base(UUID) { InventoryType = InventoryType.Wearable; } /// /// The , Skin, Shape, Skirt, Etc /// [IgnoreMember] public WearableType WearableType { get => (WearableType)Flags; set => Flags = (uint)value; } } /// /// /// InventoryAnimation Class, A bvh encoded object which animates an avatar /// [MessagePackObject] public partial class InventoryAnimation : InventoryItem { /// /// Construct an InventoryAnimation object /// /// A which becomes the /// objects UUID public InventoryAnimation(UUID UUID) : base(UUID) { InventoryType = InventoryType.Animation; } } /// /// /// InventoryGesture Class, details on a series of animations, sounds, and actions /// [MessagePackObject] public partial class InventoryGesture : InventoryItem { /// /// Construct an InventoryGesture object /// /// A which becomes the /// objects UUID public InventoryGesture(UUID UUID) : base(UUID) { InventoryType = InventoryType.Gesture; } } /// /// /// InventorySettings, LLSD settings blob as an asset /// [MessagePackObject] public partial class InventorySettings : InventoryItem { /// /// Construct an InventorySettings object /// /// A which becomes the /// objects UUID public InventorySettings(UUID UUID) : base(UUID) { InventoryType = InventoryType.Settings; } } /// /// /// InventoryMaterial, material as an asset /// [MessagePackObject] public partial class InventoryMaterial : InventoryItem { /// /// Construct an InventorySettings object /// /// A which becomes the /// objects UUID public InventoryMaterial(UUID UUID) : base(UUID) { InventoryType = InventoryType.Settings; } } /// /// /// A folder contains s and has certain attributes specific /// to itself /// [MessagePackObject] public partial class InventoryFolder : InventoryBase { public const int VERSION_UNKNOWN = -1; /// The Preferred for a folder. [Key("PreferredType")] public FolderType PreferredType; /// The Version of this folder [Key("Version")] public int Version; /// Number of child items this folder contains. [Key("DescendentCount")] public int DescendentCount; /// /// Constructor /// /// UUID of the folder public InventoryFolder(UUID UUID) : base(UUID) { PreferredType = FolderType.None; Version = 1; DescendentCount = 0; } /// /// Returns folder name /// /// Return folder name as string public override string ToString() { return Name; } /// /// Return int hash code /// /// Hash code as integer public override int GetHashCode() { return PreferredType.GetHashCode() ^ Version.GetHashCode() ^ DescendentCount.GetHashCode(); } public override bool Equals(object obj) { return obj is InventoryFolder folder && Equals(folder); } public override bool Equals(InventoryBase o) { return o is InventoryFolder folder && Equals(folder); } public bool Equals(InventoryFolder o) { return base.Equals(o as InventoryBase) && o.DescendentCount == DescendentCount && o.PreferredType == PreferredType && o.Version == Version; } /// /// Create InventoryFolder from OSD /// /// OSD Data that makes up InventoryFolder /// Inventory folder created public static InventoryFolder FromOSD(OSD data) { var res = (OSDMap)data; UUID folderId = res.TryGetValue("category_id", out var catId) ? catId : res["folder_id"]; var folder = new InventoryFolder(folderId) { UUID = res["category_id"].AsUUID(), Version = res.ContainsKey("version") ? res["version"].AsInteger() : VERSION_UNKNOWN, ParentUUID = res["parent_id"].AsUUID(), DescendentCount = res["descendents"], OwnerID = res.TryGetValue("agent_id", out var agentId) ? agentId : res["owner_id"], PreferredType = (FolderType)(sbyte)res["type_default"].AsUInteger(), Name = res["name"] }; return folder; } public void Update(OSDMap data) { if (data.ContainsKey("category_id")) { UUID = data["category_id"].AsUUID(); } if (data.ContainsKey("version")) { Version = data["version"].AsInteger(); } if (data.ContainsKey("parent_id")) { ParentUUID = data["parent_id"].AsUUID(); } if (data.ContainsKey("type_default")) { PreferredType = (FolderType)(sbyte)data["type_default"].AsUInteger(); } if (data.ContainsKey("descendents")) { DescendentCount = data["descendents"].AsInteger(); } if (data.ContainsKey("owner_id")) { OwnerID = data["owner_id"].AsUUID(); } if (data.ContainsKey("agent_id")) { OwnerID = data["agent_id"].AsUUID(); } if (data.ContainsKey("name")) { Name = data["name"].AsString(); } } /// /// Convert InventoryFolder to OSD /// /// OSD representation of InventoryFolder public override OSD GetOSD() { OSDMap res = new OSDMap(4) { ["item_id"] = UUID, ["version"] = Version, ["parent_id"] = ParentUUID, ["type"] = (sbyte)PreferredType, ["name"] = Name }; return res; } } }