/* * Copyright (c) 2006, Second Life Reverse Engineering Team * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the Second Life Reverse Engineering Team nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections; using libsecondlife; using libsecondlife.AssetSystem; using libsecondlife.Packets; namespace libsecondlife.InventorySystem { /// /// Summary description for Inventory. /// public class InventoryManager { // Reference to the SLClient Library private SecondLife slClient; // Reference to the Asset Manager private static AssetManager slAssetManager; internal AssetManager AssetManager { get{ return slAssetManager; } } // UUID of Root Inventory Folder private LLUUID uuidRootFolder; // Setup a hashtable to easily lookup folders by UUID private Hashtable htFoldersByUUID = new Hashtable(); // Setup a Hashtable to track download progress private Hashtable htFolderDownloadStatus; private ArrayList alFolderRequestQueue; private int iLastPacketRecieved; private class DescendentRequest { public LLUUID FolderID; public int Expected = int.MaxValue; public int Received = 0; public int LastReceived = 0; public bool FetchFolders = true; public bool FetchItems = true; public DescendentRequest( LLUUID folderID ) { FolderID = folderID; LastReceived = InventoryManager.getUnixtime(); } public DescendentRequest( LLUUID folderID, bool fetchFolders, bool fetchItems ) { FolderID = folderID; FetchFolders = fetchFolders; FetchItems = fetchItems; LastReceived = InventoryManager.getUnixtime(); } } // Each InventorySystem needs to be initialized with a client (for network access to SL) // and root folder. The root folder can be the root folder of an object OR an agent. public InventoryManager( SecondLife client, LLUUID rootFolder ) { slClient = client; if( slAssetManager == null ) { slAssetManager = new AssetManager( slClient ); } uuidRootFolder = rootFolder; resetFoldersByUUID(); // Setup the callback PacketCallback InventoryDescendentsCallback = new PacketCallback(InventoryDescendentsHandler); slClient.Network.RegisterCallback("InventoryDescendents", InventoryDescendentsCallback); } public AssetManager getAssetManager() { Console.WriteLine("It is not recommended that you access the asset manager directly"); return AssetManager; } private void resetFoldersByUUID() { // Init folder structure with root htFoldersByUUID = new Hashtable(); InventoryFolder ifRootFolder = new InventoryFolder(this, "My Inventory", uuidRootFolder, null); htFoldersByUUID[uuidRootFolder] = ifRootFolder; } public InventoryFolder getRootFolder() { return (InventoryFolder)htFoldersByUUID[uuidRootFolder]; } public InventoryFolder getFolder( LLUUID folderID ) { return (InventoryFolder)htFoldersByUUID[folderID]; } public InventoryFolder getFolder( String sFolderPath ) { string sSecretConst = "+@#%$#$%^%^%$^$%SV$#%FR$G"; sFolderPath = sFolderPath.Replace("//",sSecretConst); char[] seperators = new char[1]; seperators[0] = '/'; string[] sFolderPathParts = sFolderPath.Split(seperators); for( int i = 0; i 0) || (alFolderRequestQueue.Count > 0) ) { if( htFolderDownloadStatus.Count == 0 ) { DescendentRequest dr = (DescendentRequest)alFolderRequestQueue[0]; alFolderRequestQueue.RemoveAt(0); RequestFolder( dr ); } if( (getUnixtime() - iLastPacketRecieved) > 10 ) { Console.WriteLine("Time-out while waiting for packets (" + (getUnixtime() - iLastPacketRecieved) + " seconds since last packet)"); Console.WriteLine("Current Status:"); // have to make a seperate list otherwise we run into modifying the original array // while still enumerating it. ArrayList alRestartList = new ArrayList(); Console.WriteLine( htFolderDownloadStatus[0].GetType() ); foreach( DescendentRequest dr in htFolderDownloadStatus ) { Console.WriteLine( dr.FolderID + " " + dr.Expected + " / " + dr.Received + " / " + dr.LastReceived ); alRestartList.Add( dr ); } iLastPacketRecieved = getUnixtime(); foreach( DescendentRequest dr in alRestartList ) { RequestFolder( dr ); } } slClient.Tick(); } } /* Low 00333 - InventoryDescendents - Untrusted - Unencoded 1044 ItemData (Variable) 0047 GroupOwned (BOOL / 1) 0149 CRC (U32 / 1) 0159 CreationDate (S32 / 1) 0345 SaleType (U8 / 1) 0395 BaseMask (U32 / 1) 0506 Name (Variable / 1) 0562 InvType (S8 / 1) 0630 Type (S8 / 1) 0680 AssetID (LLUUID / 1) 0699 GroupID (LLUUID / 1) 0716 SalePrice (S32 / 1) 0719 OwnerID (LLUUID / 1) 0736 CreatorID (LLUUID / 1) 0968 ItemID (LLUUID / 1) 1025 FolderID (LLUUID / 1) 1084 EveryoneMask (U32 / 1) 1101 Description (Variable / 1) 1189 Flags (U32 / 1) 1348 NextOwnerMask (U32 / 1) 1452 GroupMask (U32 / 1) 1505 OwnerMask (U32 / 1) 1297 AgentData (01) 0219 AgentID (LLUUID / 1) 0366 Descendents (S32 / 1) 0418 Version (S32 / 1) 0719 OwnerID (LLUUID / 1) 1025 FolderID (LLUUID / 1) 1298 FolderData (Variable) 0506 Name (Variable / 1) 0558 ParentID (LLUUID / 1) 0630 Type (S8 / 1) 1025 FolderID (LLUUID / 1) */ public void InventoryDescendentsHandler(Packet packet, Simulator simulator) { // Console.WriteLine("Status|Queue :: " + htFolderDownloadStatus.Count + "/" + qFolderRequestQueue.Count); iLastPacketRecieved = getUnixtime(); ArrayList blocks = packet.Blocks(); InventoryItem invItem; InventoryFolder invFolder; LLUUID uuidFolderID = new LLUUID(); int iDescendentsExpected = int.MaxValue; int iDescendentsReceivedThisBlock = 0; foreach (Block block in blocks) { if( block.Layout.Name.Equals("ItemData") ) { invItem = new InventoryItem(this); foreach (Field field in block.Fields ) { switch( field.Layout.Name ) { case "Name": invItem._Name = System.Text.Encoding.UTF8.GetString( (byte[])field.Data).Trim(); invItem._Name = invItem.Name.Substring(0,invItem.Name.Length-1); break; case "Description": invItem._Description = System.Text.Encoding.UTF8.GetString( (byte[])field.Data).Trim(); invItem._Description = invItem.Description.Substring(0,invItem.Description.Length-1); break; case "InvType": invItem._InvType = sbyte.Parse(field.Data.ToString()); break; case "Type": invItem._Type = sbyte.Parse(field.Data.ToString()); break; case "SaleType": invItem._SaleType = byte.Parse(field.Data.ToString()); break; case "GroupOwned": invItem._GroupOwned = bool.Parse(field.Data.ToString()); break; case "FolderID": invItem._FolderID = new LLUUID(field.Data.ToString()); break; case "ItemID": invItem._ItemID = new LLUUID(field.Data.ToString()); break; case "AssetID": invItem._AssetID = new LLUUID(field.Data.ToString()); break; case "GroupID": invItem._GroupID = new LLUUID(field.Data.ToString()); break; case "OwnerID": invItem._OwnerID = new LLUUID(field.Data.ToString()); break; case "CreatorID": invItem._CreatorID = new LLUUID(field.Data.ToString()); break; case "CRC": invItem._CRC = uint.Parse(field.Data.ToString()); break; case "Flags": invItem._Flags = uint.Parse(field.Data.ToString()); break; case "BaseMask": invItem._BaseMask = uint.Parse(field.Data.ToString()); break; case "EveryoneMask": invItem._EveryoneMask = uint.Parse(field.Data.ToString()); break; case "NextOwnerMask": invItem._NextOwnerMask = uint.Parse(field.Data.ToString()); break; case "GroupMask": invItem._GroupMask = uint.Parse(field.Data.ToString()); break; case "OwnerMask": invItem._OwnerMask = uint.Parse(field.Data.ToString()); break; case "CreationDate": invItem._CreationDate = int.Parse(field.Data.ToString()); break; case "SalePrice": invItem._SalePrice = int.Parse(field.Data.ToString()); break; default: break; } } // There is always an item block, even if there isn't any items // the "filler" block will not have a name if( (invItem.Name != null) && !invItem.Name.Equals("") ) { iDescendentsReceivedThisBlock++; InventoryFolder ifolder = (InventoryFolder)htFoldersByUUID[invItem.FolderID]; if( ifolder.alContents.Contains( invItem ) == false ) { if( (invItem.InvType == 7) && (invItem.Type == Asset.ASSET_TYPE_NOTECARD) ) { InventoryItem temp = new InventoryNotecard( this, invItem ); invItem = temp; } if( (invItem.InvType == 0) && (invItem.Type == Asset.ASSET_TYPE_IMAGE) ) { InventoryItem temp = new InventoryImage( this, invItem ); invItem = temp; } ifolder.alContents.Add(invItem); } } } // Count number of folder descendents received if( block.Layout.Name.Equals("FolderData") ) { String name = ""; LLUUID folderid = new LLUUID(); LLUUID parentid = new LLUUID(); sbyte type = 0; foreach (Field field in block.Fields ) { switch( field.Layout.Name ) { case "Name": name = System.Text.Encoding.UTF8.GetString( (byte[])field.Data).Trim(); name = name.Substring(0,name.Length-1); break; case "FolderID": folderid = new LLUUID(field.Data.ToString()); break; case "ParentID": parentid = new LLUUID(field.Data.ToString()); break; case "Type": type = sbyte.Parse(field.Data.ToString()); break; default: break; } } invFolder = new InventoryFolder(this, name, folderid, parentid); // There is always an folder block, even if there isn't any folders // the "filler" block will not have a name if( (invFolder.Name != null) && !invFolder.Name.Equals("") ) { iDescendentsReceivedThisBlock++; // Add folder to Parent InventoryFolder ifolder = (InventoryFolder)htFoldersByUUID[invFolder.ParentID]; if( ifolder.alContents.Contains(invFolder) == false ) { ifolder.alContents.Add(invFolder); } // Add folder to UUID Lookup htFoldersByUUID[invFolder.FolderID] = invFolder; // It's not the root, should be safe to "recurse" if( !invFolder.FolderID.Equals( uuidRootFolder ) ) { bool alreadyQueued = false; foreach( DescendentRequest dr in alFolderRequestQueue ) { if( dr.FolderID == invFolder.FolderID ) { alreadyQueued = true; break; } } if( !alreadyQueued ) { alFolderRequestQueue.Add( new DescendentRequest( invFolder.FolderID ) ); } } } } // Check how many descendents we're actually supposed to receive if( block.Layout.Name.Equals("AgentData") ) { foreach (Field field in block.Fields ) { if( field.Layout.Name.Equals("Descendents") ) { iDescendentsExpected = int.Parse(field.Data.ToString()); } if( field.Layout.Name.Equals("FolderID") ) { uuidFolderID = field.Data.ToString(); // Console.WriteLine("Recieved a packet for : " + uuidFolderID); } } } } // Update download status for this folder if( iDescendentsReceivedThisBlock >= iDescendentsExpected ) { // We received all the descendents we're expecting for this folder // in this packet, so go ahead and remove folder from status list. htFolderDownloadStatus.Remove(uuidFolderID); } else { // This one packet didn't have all the descendents we're expecting // so update the total we're expecting, and update the total downloaded DescendentRequest dr = (DescendentRequest)htFolderDownloadStatus[uuidFolderID]; dr.Expected = iDescendentsExpected; dr.Received += iDescendentsReceivedThisBlock; dr.LastReceived = getUnixtime(); if( dr.Received >= dr.Expected ) { // Looks like after updating, we have all the descendents, // remove from folder status. htFolderDownloadStatus.Remove(uuidFolderID); } else { htFolderDownloadStatus[uuidFolderID] = dr; // Console.WriteLine( uuidFolderID + " is expecting " + (iDescendentsExpected - iStatus[1]) + " more packets." ); } } } public static int getUnixtime() { TimeSpan ts = (DateTime.UtcNow - new DateTime(1970,1,1,0,0,0)); return (int)ts.TotalSeconds; } } }