diff --git a/libsecondlife/AppearanceManager.cs b/libsecondlife/AppearanceManager.cs
index 803918f4..5eb41815 100644
--- a/libsecondlife/AppearanceManager.cs
+++ b/libsecondlife/AppearanceManager.cs
@@ -1,509 +1,508 @@
-/*
- * Copyright (c) 2007, 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.Text;
-using System.Drawing;
-using System.Drawing.Imaging;
-using System.Collections.Generic;
-using System.Threading;
-using System.IO;
-using libsecondlife;
-using libsecondlife.Packets;
-using libsecondlife.InventorySystem;
-using libsecondlife.Baking;
-
-namespace libsecondlife
-{
- ///
- ///
- ///
- public class Wearable
- {
- ///
- ///
- ///
- public enum WearableType : byte
- {
- ///
- Shape = 0,
- ///
- Skin,
- ///
- Hair,
- ///
- Eyes,
- ///
- Shirt,
- ///
- Pants,
- ///
- Shoes,
- ///
- Socks,
- ///
- Jacket,
- ///
- Gloves,
- ///
- Undershirt,
- ///
- Underpants,
- ///
- Skirt,
- ///
- Invalid = 255
- };
-
- ///
- ///
- ///
- public enum ForSale
- {
- /// Not for sale
- Not = 0,
- /// The original is for sale
- Original = 1,
- /// Copies are for sale
- Copy = 2,
- /// The contents of the object are for sale
- Contents = 3
- }
-
-
- public string Name = String.Empty;
- public string Description = String.Empty;
- public WearableType Type = WearableType.Shape;
- public ForSale Sale = ForSale.Not;
- public int SalePrice = 0;
- public LLUUID Creator = LLUUID.Zero;
- public LLUUID Owner = LLUUID.Zero;
- public LLUUID LastOwner = LLUUID.Zero;
- public LLUUID Group = LLUUID.Zero;
- public bool GroupOwned = false;
- public Permissions Permissions;
- public Dictionary Params = new Dictionary();
- public Dictionary Textures = new Dictionary();
-
-
- private SecondLife Client;
- private string[] ForSaleNames = new string[]
- {
- "not",
- "orig",
- "copy",
- "cntn"
- };
-
-
- ///
- /// Default constructor
- ///
- /// Reference to the SecondLife client
- public Wearable(SecondLife client)
- {
- Client = client;
- }
-
- public static AssetType WearableTypeToAssetType(WearableType type)
- {
- switch (type)
- {
- case WearableType.Shape:
- case WearableType.Skin:
- case WearableType.Hair:
- case WearableType.Eyes:
- return AssetType.Bodypart;
- case WearableType.Shirt:
- case WearableType.Pants:
- case WearableType.Shoes:
- case WearableType.Socks:
- case WearableType.Jacket:
- case WearableType.Gloves:
- case WearableType.Undershirt:
- case WearableType.Underpants:
- return AssetType.Clothing;
- default:
- return AssetType.Unknown;
- }
- }
-
- public bool ImportAsset(string data)
- {
- int version = -1;
- int n = -1;
-
- try
- {
- n = data.IndexOf('\n');
- version = Int32.Parse(data.Substring(19, n - 18));
- data = data.Remove(0, n);
-
- if (version != 22)
- {
- Client.Log("Wearable asset has unrecognized version " + version, Helpers.LogLevel.Warning);
- return false;
- }
-
- n = data.IndexOf('\n');
- Name = data.Substring(0, n);
- data = data.Remove(0, n);
-
- n = data.IndexOf('\n');
- Description = data.Substring(0, n);
- data = data.Remove(0, n);
-
- // Split in to an upper and lower half
- string[] parts = data.Split(new string[] { "parameters" }, StringSplitOptions.None);
- parts[1] = "parameters" + parts[1];
-
- Permissions = new Permissions();
-
- // Parse the upper half
- string[] lines = parts[0].Split('\n');
- foreach (string thisline in lines)
- {
- string line = thisline.Trim();
- string[] fields = line.Split('\t');
-
- if (fields.Length == 2)
- {
- if (fields[0] == "creator_mask")
- {
- // Deprecated, apply this as the base mask
- Permissions.BaseMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
- }
- else if (fields[0] == "base_mask")
- {
- Permissions.BaseMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
- }
- else if (fields[0] == "owner_mask")
- {
- Permissions.OwnerMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
- }
- else if (fields[0] == "group_mask")
- {
- Permissions.GroupMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
- }
- else if (fields[0] == "everyone_mask")
- {
- Permissions.EveryoneMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
- }
- else if (fields[0] == "next_owner_mask")
- {
- Permissions.NextOwnerMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
- }
- else if (fields[0] == "creator_id")
- {
- Creator = new LLUUID(fields[1]);
- }
- else if (fields[0] == "owner_id")
- {
- Owner = new LLUUID(fields[1]);
- }
- else if (fields[0] == "last_owner_id")
- {
- LastOwner = new LLUUID(fields[1]);
- }
- else if (fields[0] == "group_id")
- {
- Group = new LLUUID(fields[1]);
- }
- else if (fields[0] == "group_owned")
- {
- GroupOwned = (Int32.Parse(fields[1]) != 0);
- }
- else if (fields[0] == "sale_type")
- {
- for (int i = 0; i < ForSaleNames.Length; i++)
- {
- if (fields[1] == ForSaleNames[i])
- {
- Sale = (ForSale)i;
- break;
- }
- }
- }
- else if (fields[0] == "sale_price")
- {
- SalePrice = Int32.Parse(fields[1]);
- }
- else if (fields[0] == "perm_mask")
- {
- Client.Log("Wearable asset has deprecated perm_mask field, ignoring", Helpers.LogLevel.Warning);
- }
- }
- else if (line.StartsWith("type "))
- {
- Type = (WearableType)Int32.Parse(line.Substring(5));
- break;
- }
- }
-
- // Break up the lower half in to parameters and textures
- string[] lowerparts = parts[1].Split(new string[] { "textures" }, StringSplitOptions.None);
- lowerparts[1] = "textures" + lowerparts[1];
-
- // Parse the parameters
- lines = lowerparts[0].Split('\n');
- foreach (string line in lines)
- {
- string[] fields = line.Split(' ');
-
- // Use exception handling to deal with all the lines we aren't interested in
- try
- {
- int id = Int32.Parse(fields[0]);
- float weight = Single.Parse(fields[1], System.Globalization.NumberStyles.Float,
- Helpers.EnUsCulture.NumberFormat);
-
- Params[id] = weight;
- }
- catch (Exception)
- {
- }
- }
-
- // Parse the textures
- lines = lowerparts[1].Split('\n');
- foreach (string line in lines)
- {
- string[] fields = line.Split(' ');
-
- // Use exception handling to deal with all the lines we aren't interested in
- try
- {
- AppearanceManager.TextureIndex id = (AppearanceManager.TextureIndex)Int32.Parse(fields[0]);
- LLUUID texture = new LLUUID(fields[1]);
-
- Textures[id] = texture;
- }
- catch (Exception)
- {
- }
- }
-
- return true;
- }
- catch (Exception e)
- {
- Client.Log("Failed to parse wearable asset: " + e.ToString(), Helpers.LogLevel.Warning);
- }
-
- return false;
- }
-
- public string ExportAsset()
- {
- StringBuilder data = new StringBuilder("LLWearable version 22\n");
- data.Append(Name); data.Append("\n\n");
- data.Append("\tpermissions 0\n\t{\n");
- data.Append("\t\tbase_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.BaseMask)); data.Append("\n");
- data.Append("\t\towner_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.OwnerMask)); data.Append("\n");
- data.Append("\t\tgroup_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.GroupMask)); data.Append("\n");
- data.Append("\t\teveryone_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.EveryoneMask)); data.Append("\n");
- data.Append("\t\tnext_owner_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.NextOwnerMask)); data.Append("\n");
- data.Append("\t\tcreator_id\t"); data.Append(Creator.ToStringHyphenated()); data.Append("\n");
- data.Append("\t\towner_id\t"); data.Append(Owner.ToStringHyphenated()); data.Append("\n");
- data.Append("\t\tlast_owner_id\t"); data.Append(LastOwner.ToStringHyphenated()); data.Append("\n");
- data.Append("\t\tgroup_id\t"); data.Append(Group.ToStringHyphenated()); data.Append("\n");
- if (GroupOwned) data.Append("\t\tgroup_owned\t1\n");
- data.Append("\t}\n");
- data.Append("\tsale_info\t0\n");
- data.Append("\t{\n");
- data.Append("\t\tsale_type\t"); data.Append(ForSaleNames[(int)Sale]); data.Append("\n");
- data.Append("\t\tsale_price\t"); data.Append(SalePrice); data.Append("\n");
- data.Append("\t}\n");
- data.Append("type "); data.Append((int)Type); data.Append("\n");
-
- data.Append("parameters "); data.Append(Params.Count); data.Append("\n");
- foreach (KeyValuePair param in Params)
- {
- data.Append(param.Key); data.Append(" "); data.Append(Helpers.FloatToTerseString(param.Value)); data.Append("\n");
- }
-
- data.Append("textures "); data.Append(Textures.Count); data.Append("\n");
- foreach (KeyValuePair texture in Textures)
- {
- data.Append(texture.Key); data.Append(" "); data.Append(texture.Value.ToStringHyphenated()); data.Append("\n");
- }
-
- return data.ToString();
- }
-
- ///
- /// Create a new Wearable from an AssetWearable
- ///
- /// SecondLife client
- /// AssetWearable to convert
- ///
-
- public static Wearable FromAssetWearable(SecondLife client, libsecondlife.AssetSystem.AssetWearable aw)
- {
- Wearable w = new Wearable(client);
- w.Creator = aw.Creator_ID;
- w.Description = aw.Description;
- w.Group = aw.Group_ID;
- w.GroupOwned = aw.Group_Owned;
- w.LastOwner = aw.Last_Owner_ID;
- w.Name = aw.Name;
- w.Owner = aw.Owner_ID;
- w.Params = new Dictionary(aw.Parameters);
- w.SalePrice = (int)aw.Sale_Price;
- w.Textures = new Dictionary(aw.Textures.Count);
-
- foreach (KeyValuePair i in aw.Textures)
- w.Textures.Add((AppearanceManager.TextureIndex)i.Key, i.Value);
-
- w.Permissions.BaseMask = (PermissionMask)aw.Permission_Base_Mask;
- w.Permissions.EveryoneMask = (PermissionMask)aw.Permission_Everyone_Mask;
- w.Permissions.GroupMask = (PermissionMask)aw.Permission_Group_Mask;
- w.Permissions.NextOwnerMask = (PermissionMask)aw.Permission_Next_Owner_Mask;
- w.Permissions.OwnerMask = (PermissionMask)aw.Permission_Owner_Mask;
-
- w.Type = (Wearable.WearableType)aw.AppearanceLayer; // assumes these two enums are identical
-
- return w;
- }
- }
-
- ///
- ///
- ///
- public struct WearableData
- {
- public Wearable Wearable;
- public LLUUID AssetID;
- public LLUUID ItemID;
-
- public static WearableData FromInventoryWearable(SecondLife client, InventorySystem.InventoryWearable iw)
- {
- WearableData wd = new WearableData();
- wd.Wearable = Wearable.FromAssetWearable(client,(libsecondlife.AssetSystem.AssetWearable)iw.Asset);
- wd.AssetID = iw.AssetID;
- wd.ItemID = iw.ItemID;
-
- return wd;
- }
- }
-
- ///
- ///
- ///
- public class AppearanceManager
- {
- ///
- ///
- ///
- public enum TextureIndex
- {
- Unknown = -1,
- HeadBodypaint = 0,
- UpperShirt,
- LowerPants,
- EyesIris,
- Hair,
- UpperBodypaint,
- LowerBodypaint,
- LowerShoes,
- HeadBaked,
- UpperBaked,
- LowerBaked,
- EyesBaked,
- LowerSocks,
- UpperJacket,
- LowerJacket,
- UpperGloves,
- UpperUndershirt,
- LowerUnderpants,
- Skirt,
- SkirtBaked
- }
-
- ///
- ///
- ///
- public enum BakeType
- {
- Unknown = -1,
- Head = 0,
- UpperBody = 1,
- LowerBody = 2,
- Eyes = 3,
- Skirt = 4
- }
-
-
- ///
- ///
- ///
- /// A mapping of WearableTypes to KeyValuePairs
- /// with Asset ID of the wearable as key and Item ID as value
- public delegate void AgentWearablesCallback(Dictionary> wearables);
-
-
- ///
- public event AgentWearablesCallback OnAgentWearables;
-
- /// Total number of wearables for each avatar
- public const int WEARABLE_COUNT = 13;
- ///
- public const int BAKED_TEXTURE_COUNT = 5;
- ///
- public const int WEARABLES_PER_LAYER = 7;
- ///
- public const int AVATAR_TEXTURE_COUNT = 20;
- /// Map of what wearables are included in each bake
- public static readonly Wearable.WearableType[][] WEARABLE_BAKE_MAP = new Wearable.WearableType[][]
- {
- new Wearable.WearableType[] { Wearable.WearableType.Shape, Wearable.WearableType.Skin, Wearable.WearableType.Hair, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid },
- new Wearable.WearableType[] { Wearable.WearableType.Shape, Wearable.WearableType.Skin, Wearable.WearableType.Shirt, Wearable.WearableType.Jacket, Wearable.WearableType.Gloves, Wearable.WearableType.Undershirt, Wearable.WearableType.Invalid },
- new Wearable.WearableType[] { Wearable.WearableType.Shape, Wearable.WearableType.Skin, Wearable.WearableType.Pants, Wearable.WearableType.Shoes, Wearable.WearableType.Socks, Wearable.WearableType.Jacket, Wearable.WearableType.Underpants },
- new Wearable.WearableType[] { Wearable.WearableType.Eyes, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid },
- new Wearable.WearableType[] { Wearable.WearableType.Skin, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid }
- };
- /// Secret values to finalize the cache check hashes for each
- /// bake
- public static readonly LLUUID[] BAKED_TEXTURE_HASH = new LLUUID[]
- {
- new LLUUID("18ded8d6-bcfc-e415-8539-944c0f5ea7a6"),
- new LLUUID("338c29e3-3024-4dbb-998d-7c04cf4fa88f"),
- new LLUUID("91b4a2c7-1b1a-ba16-9a16-1f8f8dcc1c3f"),
- new LLUUID("b2cf28af-b840-1071-3c6a-78085d8128b5"),
- new LLUUID("ea800387-ea1a-14e0-56cb-24f2022f969a")
- };
- /// Default avatar texture, used to detect when a custom
- /// texture is not set for a face
- public static readonly LLUUID DEFAULT_AVATAR_TEXTURE = new LLUUID("c228d1cf-4b5d-4ba8-84f4-899a0796aa97");
-
-
- private SecondLife Client;
- private AssetManager Assets;
- private Dictionary Wearables = new Dictionary();
- // As wearable assets are downloaded and decoded, the textures are added to this array
+/*
+ * Copyright (c) 2007, 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.Text;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Collections.Generic;
+using System.Threading;
+using System.IO;
+using libsecondlife;
+using libsecondlife.Packets;
+using libsecondlife.Baking;
+
+namespace libsecondlife
+{
+ ///
+ ///
+ ///
+ public class Wearable
+ {
+ ///
+ ///
+ ///
+ public enum WearableType : byte
+ {
+ ///
+ Shape = 0,
+ ///
+ Skin,
+ ///
+ Hair,
+ ///
+ Eyes,
+ ///
+ Shirt,
+ ///
+ Pants,
+ ///
+ Shoes,
+ ///
+ Socks,
+ ///
+ Jacket,
+ ///
+ Gloves,
+ ///
+ Undershirt,
+ ///
+ Underpants,
+ ///
+ Skirt,
+ ///
+ Invalid = 255
+ };
+
+ ///
+ ///
+ ///
+ public enum ForSale
+ {
+ /// Not for sale
+ Not = 0,
+ /// The original is for sale
+ Original = 1,
+ /// Copies are for sale
+ Copy = 2,
+ /// The contents of the object are for sale
+ Contents = 3
+ }
+
+
+ public string Name = String.Empty;
+ public string Description = String.Empty;
+ public WearableType Type = WearableType.Shape;
+ public ForSale Sale = ForSale.Not;
+ public int SalePrice = 0;
+ public LLUUID Creator = LLUUID.Zero;
+ public LLUUID Owner = LLUUID.Zero;
+ public LLUUID LastOwner = LLUUID.Zero;
+ public LLUUID Group = LLUUID.Zero;
+ public bool GroupOwned = false;
+ public Permissions Permissions;
+ public Dictionary Params = new Dictionary();
+ public Dictionary Textures = new Dictionary();
+
+
+ private SecondLife Client;
+ private string[] ForSaleNames = new string[]
+ {
+ "not",
+ "orig",
+ "copy",
+ "cntn"
+ };
+
+
+ ///
+ /// Default constructor
+ ///
+ /// Reference to the SecondLife client
+ public Wearable(SecondLife client)
+ {
+ Client = client;
+ }
+
+ public static AssetType WearableTypeToAssetType(WearableType type)
+ {
+ switch (type)
+ {
+ case WearableType.Shape:
+ case WearableType.Skin:
+ case WearableType.Hair:
+ case WearableType.Eyes:
+ return AssetType.Bodypart;
+ case WearableType.Shirt:
+ case WearableType.Pants:
+ case WearableType.Shoes:
+ case WearableType.Socks:
+ case WearableType.Jacket:
+ case WearableType.Gloves:
+ case WearableType.Undershirt:
+ case WearableType.Underpants:
+ return AssetType.Clothing;
+ default:
+ return AssetType.Unknown;
+ }
+ }
+
+ public bool ImportAsset(string data)
+ {
+ int version = -1;
+ int n = -1;
+
+ try
+ {
+ n = data.IndexOf('\n');
+ version = Int32.Parse(data.Substring(19, n - 18));
+ data = data.Remove(0, n);
+
+ if (version != 22)
+ {
+ Client.Log("Wearable asset has unrecognized version " + version, Helpers.LogLevel.Warning);
+ return false;
+ }
+
+ n = data.IndexOf('\n');
+ Name = data.Substring(0, n);
+ data = data.Remove(0, n);
+
+ n = data.IndexOf('\n');
+ Description = data.Substring(0, n);
+ data = data.Remove(0, n);
+
+ // Split in to an upper and lower half
+ string[] parts = data.Split(new string[] { "parameters" }, StringSplitOptions.None);
+ parts[1] = "parameters" + parts[1];
+
+ Permissions = new Permissions();
+
+ // Parse the upper half
+ string[] lines = parts[0].Split('\n');
+ foreach (string thisline in lines)
+ {
+ string line = thisline.Trim();
+ string[] fields = line.Split('\t');
+
+ if (fields.Length == 2)
+ {
+ if (fields[0] == "creator_mask")
+ {
+ // Deprecated, apply this as the base mask
+ Permissions.BaseMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
+ }
+ else if (fields[0] == "base_mask")
+ {
+ Permissions.BaseMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
+ }
+ else if (fields[0] == "owner_mask")
+ {
+ Permissions.OwnerMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
+ }
+ else if (fields[0] == "group_mask")
+ {
+ Permissions.GroupMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
+ }
+ else if (fields[0] == "everyone_mask")
+ {
+ Permissions.EveryoneMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
+ }
+ else if (fields[0] == "next_owner_mask")
+ {
+ Permissions.NextOwnerMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
+ }
+ else if (fields[0] == "creator_id")
+ {
+ Creator = new LLUUID(fields[1]);
+ }
+ else if (fields[0] == "owner_id")
+ {
+ Owner = new LLUUID(fields[1]);
+ }
+ else if (fields[0] == "last_owner_id")
+ {
+ LastOwner = new LLUUID(fields[1]);
+ }
+ else if (fields[0] == "group_id")
+ {
+ Group = new LLUUID(fields[1]);
+ }
+ else if (fields[0] == "group_owned")
+ {
+ GroupOwned = (Int32.Parse(fields[1]) != 0);
+ }
+ else if (fields[0] == "sale_type")
+ {
+ for (int i = 0; i < ForSaleNames.Length; i++)
+ {
+ if (fields[1] == ForSaleNames[i])
+ {
+ Sale = (ForSale)i;
+ break;
+ }
+ }
+ }
+ else if (fields[0] == "sale_price")
+ {
+ SalePrice = Int32.Parse(fields[1]);
+ }
+ else if (fields[0] == "perm_mask")
+ {
+ Client.Log("Wearable asset has deprecated perm_mask field, ignoring", Helpers.LogLevel.Warning);
+ }
+ }
+ else if (line.StartsWith("type "))
+ {
+ Type = (WearableType)Int32.Parse(line.Substring(5));
+ break;
+ }
+ }
+
+ // Break up the lower half in to parameters and textures
+ string[] lowerparts = parts[1].Split(new string[] { "textures" }, StringSplitOptions.None);
+ lowerparts[1] = "textures" + lowerparts[1];
+
+ // Parse the parameters
+ lines = lowerparts[0].Split('\n');
+ foreach (string line in lines)
+ {
+ string[] fields = line.Split(' ');
+
+ // Use exception handling to deal with all the lines we aren't interested in
+ try
+ {
+ int id = Int32.Parse(fields[0]);
+ float weight = Single.Parse(fields[1], System.Globalization.NumberStyles.Float,
+ Helpers.EnUsCulture.NumberFormat);
+
+ Params[id] = weight;
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ // Parse the textures
+ lines = lowerparts[1].Split('\n');
+ foreach (string line in lines)
+ {
+ string[] fields = line.Split(' ');
+
+ // Use exception handling to deal with all the lines we aren't interested in
+ try
+ {
+ AppearanceManager.TextureIndex id = (AppearanceManager.TextureIndex)Int32.Parse(fields[0]);
+ LLUUID texture = new LLUUID(fields[1]);
+
+ Textures[id] = texture;
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ Client.Log("Failed to parse wearable asset: " + e.ToString(), Helpers.LogLevel.Warning);
+ }
+
+ return false;
+ }
+
+ public string ExportAsset()
+ {
+ StringBuilder data = new StringBuilder("LLWearable version 22\n");
+ data.Append(Name); data.Append("\n\n");
+ data.Append("\tpermissions 0\n\t{\n");
+ data.Append("\t\tbase_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.BaseMask)); data.Append("\n");
+ data.Append("\t\towner_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.OwnerMask)); data.Append("\n");
+ data.Append("\t\tgroup_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.GroupMask)); data.Append("\n");
+ data.Append("\t\teveryone_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.EveryoneMask)); data.Append("\n");
+ data.Append("\t\tnext_owner_mask\t"); data.Append(Helpers.UIntToHexString((uint)Permissions.NextOwnerMask)); data.Append("\n");
+ data.Append("\t\tcreator_id\t"); data.Append(Creator.ToStringHyphenated()); data.Append("\n");
+ data.Append("\t\towner_id\t"); data.Append(Owner.ToStringHyphenated()); data.Append("\n");
+ data.Append("\t\tlast_owner_id\t"); data.Append(LastOwner.ToStringHyphenated()); data.Append("\n");
+ data.Append("\t\tgroup_id\t"); data.Append(Group.ToStringHyphenated()); data.Append("\n");
+ if (GroupOwned) data.Append("\t\tgroup_owned\t1\n");
+ data.Append("\t}\n");
+ data.Append("\tsale_info\t0\n");
+ data.Append("\t{\n");
+ data.Append("\t\tsale_type\t"); data.Append(ForSaleNames[(int)Sale]); data.Append("\n");
+ data.Append("\t\tsale_price\t"); data.Append(SalePrice); data.Append("\n");
+ data.Append("\t}\n");
+ data.Append("type "); data.Append((int)Type); data.Append("\n");
+
+ data.Append("parameters "); data.Append(Params.Count); data.Append("\n");
+ foreach (KeyValuePair param in Params)
+ {
+ data.Append(param.Key); data.Append(" "); data.Append(Helpers.FloatToTerseString(param.Value)); data.Append("\n");
+ }
+
+ data.Append("textures "); data.Append(Textures.Count); data.Append("\n");
+ foreach (KeyValuePair texture in Textures)
+ {
+ data.Append(texture.Key); data.Append(" "); data.Append(texture.Value.ToStringHyphenated()); data.Append("\n");
+ }
+
+ return data.ToString();
+ }
+
+ ///
+ /// Create a new Wearable from an AssetWearable
+ ///
+ /// SecondLife client
+ /// AssetWearable to convert
+ ///
+
+ /*public static Wearable FromAssetWearable(SecondLife client, libsecondlife.AssetSystem.AssetWearable aw)
+ {
+ Wearable w = new Wearable(client);
+ w.Creator = aw.Creator_ID;
+ w.Description = aw.Description;
+ w.Group = aw.Group_ID;
+ w.GroupOwned = aw.Group_Owned;
+ w.LastOwner = aw.Last_Owner_ID;
+ w.Name = aw.Name;
+ w.Owner = aw.Owner_ID;
+ w.Params = new Dictionary(aw.Parameters);
+ w.SalePrice = (int)aw.Sale_Price;
+ w.Textures = new Dictionary(aw.Textures.Count);
+
+ foreach (KeyValuePair i in aw.Textures)
+ w.Textures.Add((AppearanceManager.TextureIndex)i.Key, i.Value);
+
+ w.Permissions.BaseMask = (PermissionMask)aw.Permission_Base_Mask;
+ w.Permissions.EveryoneMask = (PermissionMask)aw.Permission_Everyone_Mask;
+ w.Permissions.GroupMask = (PermissionMask)aw.Permission_Group_Mask;
+ w.Permissions.NextOwnerMask = (PermissionMask)aw.Permission_Next_Owner_Mask;
+ w.Permissions.OwnerMask = (PermissionMask)aw.Permission_Owner_Mask;
+
+ w.Type = (Wearable.WearableType)aw.AppearanceLayer; // assumes these two enums are identical
+
+ return w;
+ }*/
+ }
+
+ ///
+ ///
+ ///
+ public struct WearableData
+ {
+ public Wearable Wearable;
+ public LLUUID AssetID;
+ public LLUUID ItemID;
+
+ /*public static WearableData FromInventoryWearable(SecondLife client, InventorySystem.InventoryWearable iw)
+ {
+ WearableData wd = new WearableData();
+ wd.Wearable = Wearable.FromAssetWearable(client,(libsecondlife.AssetSystem.AssetWearable)iw.Asset);
+ wd.AssetID = iw.AssetID;
+ wd.ItemID = iw.ItemID;
+
+ return wd;
+ }*/
+ }
+
+ ///
+ ///
+ ///
+ public class AppearanceManager
+ {
+ ///
+ ///
+ ///
+ public enum TextureIndex
+ {
+ Unknown = -1,
+ HeadBodypaint = 0,
+ UpperShirt,
+ LowerPants,
+ EyesIris,
+ Hair,
+ UpperBodypaint,
+ LowerBodypaint,
+ LowerShoes,
+ HeadBaked,
+ UpperBaked,
+ LowerBaked,
+ EyesBaked,
+ LowerSocks,
+ UpperJacket,
+ LowerJacket,
+ UpperGloves,
+ UpperUndershirt,
+ LowerUnderpants,
+ Skirt,
+ SkirtBaked
+ }
+
+ ///
+ ///
+ ///
+ public enum BakeType
+ {
+ Unknown = -1,
+ Head = 0,
+ UpperBody = 1,
+ LowerBody = 2,
+ Eyes = 3,
+ Skirt = 4
+ }
+
+
+ ///
+ ///
+ ///
+ /// A mapping of WearableTypes to KeyValuePairs
+ /// with Asset ID of the wearable as key and Item ID as value
+ public delegate void AgentWearablesCallback(Dictionary> wearables);
+
+
+ ///
+ public event AgentWearablesCallback OnAgentWearables;
+
+ /// Total number of wearables for each avatar
+ public const int WEARABLE_COUNT = 13;
+ ///
+ public const int BAKED_TEXTURE_COUNT = 5;
+ ///
+ public const int WEARABLES_PER_LAYER = 7;
+ ///
+ public const int AVATAR_TEXTURE_COUNT = 20;
+ /// Map of what wearables are included in each bake
+ public static readonly Wearable.WearableType[][] WEARABLE_BAKE_MAP = new Wearable.WearableType[][]
+ {
+ new Wearable.WearableType[] { Wearable.WearableType.Shape, Wearable.WearableType.Skin, Wearable.WearableType.Hair, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid },
+ new Wearable.WearableType[] { Wearable.WearableType.Shape, Wearable.WearableType.Skin, Wearable.WearableType.Shirt, Wearable.WearableType.Jacket, Wearable.WearableType.Gloves, Wearable.WearableType.Undershirt, Wearable.WearableType.Invalid },
+ new Wearable.WearableType[] { Wearable.WearableType.Shape, Wearable.WearableType.Skin, Wearable.WearableType.Pants, Wearable.WearableType.Shoes, Wearable.WearableType.Socks, Wearable.WearableType.Jacket, Wearable.WearableType.Underpants },
+ new Wearable.WearableType[] { Wearable.WearableType.Eyes, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid },
+ new Wearable.WearableType[] { Wearable.WearableType.Skin, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid }
+ };
+ /// Secret values to finalize the cache check hashes for each
+ /// bake
+ public static readonly LLUUID[] BAKED_TEXTURE_HASH = new LLUUID[]
+ {
+ new LLUUID("18ded8d6-bcfc-e415-8539-944c0f5ea7a6"),
+ new LLUUID("338c29e3-3024-4dbb-998d-7c04cf4fa88f"),
+ new LLUUID("91b4a2c7-1b1a-ba16-9a16-1f8f8dcc1c3f"),
+ new LLUUID("b2cf28af-b840-1071-3c6a-78085d8128b5"),
+ new LLUUID("ea800387-ea1a-14e0-56cb-24f2022f969a")
+ };
+ /// Default avatar texture, used to detect when a custom
+ /// texture is not set for a face
+ public static readonly LLUUID DEFAULT_AVATAR_TEXTURE = new LLUUID("c228d1cf-4b5d-4ba8-84f4-899a0796aa97");
+
+
+ private SecondLife Client;
+ private AssetManager Assets;
+ private Dictionary Wearables = new Dictionary();
+ // As wearable assets are downloaded and decoded, the textures are added to this array
private LLUUID[] AgentTextures = new LLUUID[AVATAR_TEXTURE_COUNT];
protected struct PendingAssetDownload
@@ -516,931 +515,931 @@ namespace libsecondlife
Id = id;
Type = type;
}
- }
-
- // Wearable assets are downloaded one at a time, a new request is pulled off the queue
+ }
+
+ // Wearable assets are downloaded one at a time, a new request is pulled off the queue
// and started when the previous one completes
- private Queue DownloadQueue = new Queue();
- // A list of all the images we are currently downloading, prior to baking
- private Dictionary ImageDownloads = new Dictionary();
- // A list of all the bakes we need to complete
- private Dictionary PendingBakes = new Dictionary(BAKED_TEXTURE_COUNT);
- // A list of all the uploads that are in progress
- private Dictionary PendingUploads = new Dictionary(BAKED_TEXTURE_COUNT);
- // Whether the handler for our current wearable list should automatically start downloading the assets
- private bool DownloadWearables = false;
- private static int CacheCheckSerialNum = 1; //FIXME
- private static uint SetAppearanceSerialNum = 1; //FIXME
- private AutoResetEvent WearablesDownloadedEvent = new AutoResetEvent(false);
- private AutoResetEvent CachedResponseEvent = new AutoResetEvent(false);
- // FIXME: Create a class-level appearance thread so multiple threads can't be launched
-
- ///
- /// Default constructor
- ///
- ///
- ///
- public AppearanceManager(SecondLife client, AssetManager assets)
- {
- Client = client;
- Assets = assets;
-
- // Initialize AgentTextures to zero UUIDs
- for (int i = 0; i < AgentTextures.Length; i++)
- AgentTextures[i] = LLUUID.Zero;
-
- Client.Network.RegisterCallback(PacketType.AgentWearablesUpdate, new NetworkManager.PacketCallback(AgentWearablesHandler));
- Client.Network.RegisterCallback(PacketType.AgentCachedTextureResponse, new NetworkManager.PacketCallback(AgentCachedTextureResponseHandler));
- }
-
- ///
- /// If the appearance thread is running it is terminated here
- ///
- ~AppearanceManager()
- {
- WearablesDownloadedEvent.Set();
- CachedResponseEvent.Set();
- }
-
- ///
- /// Returns the assetID for a given WearableType
- ///
- ///
- ///
- public LLUUID GetWearableAsset(Wearable.WearableType type)
- {
- if (Wearables.ContainsKey(type))
- return Wearables[type].AssetID;
- else
- return LLUUID.Zero;
- }
-
- ///
- ///
- ///
- ///
- public void SetPreviousAppearance()
- {
- // Clear out any previous data
- DownloadWearables = false;
- lock (Wearables) Wearables.Clear();
- lock (AgentTextures)
- {
- for (int i = 0; i < AgentTextures.Length; i++)
- AgentTextures[i] = LLUUID.Zero;
- }
- lock (DownloadQueue) DownloadQueue.Clear();
-
- Thread appearanceThread = new Thread(new ThreadStart(StartSetPreviousAppearance));
- appearanceThread.Start();
- }
-
- ///
- /// Add a single wearable to your outfit, replacing if nessesary.
- ///
- ///
- public void Wear(libsecondlife.InventorySystem.InventoryWearable wearable)
- {
- List x = new List();
- x.Add(wearable);
- Wear(x);
- }
-
- public void Wear(List iws)
- {
- DownloadWearables = false;
-
- lock (Wearables)
- {
- Dictionary preserve = new Dictionary(4);
-
- foreach (KeyValuePair kvp in Wearables)
- {
- if (
- kvp.Key == Wearable.WearableType.Shape ||
- kvp.Key == Wearable.WearableType.Skin ||
- kvp.Key == Wearable.WearableType.Eyes ||
- kvp.Key == Wearable.WearableType.Hair
- )
- {
- preserve.Add(kvp.Key,kvp.Value);
- Client.DebugLog("Keeping " + kvp.Key.ToString() + " " + kvp.Value.Wearable.Name);
- }
- }
-
- Wearables = preserve;
-
- foreach (libsecondlife.InventorySystem.InventoryWearable iw in iws)
- {
- WearableData wd = WearableData.FromInventoryWearable(Client,iw);
- Wearables[wd.Wearable.Type] = wd;
- Client.DebugLog("Found " + iw.Name);
- }
- }
-
- lock (DownloadQueue) DownloadQueue.Clear();
- lock (ImageDownloads) ImageDownloads.Clear();
- lock (PendingBakes) PendingBakes.Clear();
- lock (PendingUploads) PendingUploads.Clear();
-
- lock (AgentTextures)
- {
- for (int i = 0; i < AgentTextures.Length; i++)
- AgentTextures[i] = LLUUID.Zero;
- }
-
- Thread appearanceThread = new Thread(new ThreadStart(StartWear));
- appearanceThread.Start();
- }
-
- public void WearOutfit(InventoryFolder folder)
- {
- Thread wearOutfitThread = new Thread(new ParameterizedThreadStart(WearOutfitAsync));
- wearOutfitThread.Start(folder);
- }
-
- public void WearOutfit(string folder)
- {
- Thread wearOutfitThread = new Thread(new ParameterizedThreadStart(WearOutfitAsync));
- wearOutfitThread.Start(folder);
- }
-
- public void WearOutfitAsync(object _folder)
- {
- InventoryFolder folder;
-
- if (_folder is string)
- folder = Client.Inventory.getFolder((string)_folder);
- else
- folder = (InventoryFolder)_folder;
-
- List iws = new List();
- folder.RequestDownloadContents(false, false, true).RequestComplete.WaitOne();
-
- foreach (InventoryBase ib in folder.GetContents())
- {
- if (ib is InventoryWearable)
- iws.Add((InventoryWearable)ib);
- }
-
- Wear(iws);
- }
-
- ///
- /// Build hashes out of the texture assetIDs for each baking layer to
- /// ask the simulator whether it has cached copies of each baked texture
- ///
- public void RequestCachedBakes()
- {
- Client.DebugLog("RequestCachedBakes()");
-
- List> hashes = new List>();
-
- AgentCachedTexturePacket cache = new AgentCachedTexturePacket();
- cache.AgentData.AgentID = Client.Network.AgentID;
- cache.AgentData.SessionID = Client.Network.SessionID;
- cache.AgentData.SerialNum = CacheCheckSerialNum;
-
- // Build hashes for each of the bake layers from the individual components
- for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
- {
- // Don't do a cache request for a skirt bake if we're not wearing a skirt
- if (bakedIndex == (int)BakeType.Skirt &&
- (!Wearables.ContainsKey(Wearable.WearableType.Skirt) || Wearables[Wearable.WearableType.Skirt].AssetID == LLUUID.Zero))
- continue;
-
- LLUUID hash = new LLUUID();
-
- for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
- {
- Wearable.WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
- LLUUID assetID = GetWearableAsset(type);
-
- // Build a hash of all the texture asset IDs in this baking layer
- if (assetID != null) hash ^= assetID;
- }
-
- if (hash != LLUUID.Zero)
- {
- // Hash with our secret value for this baked layer
- hash ^= BAKED_TEXTURE_HASH[bakedIndex];
-
- // Add this to the list of hashes to send out
- hashes.Add(new KeyValuePair(bakedIndex, hash));
- }
- }
-
- // Only send the packet out if there's something to check
- if (hashes.Count > 0)
- {
- cache.WearableData = new AgentCachedTexturePacket.WearableDataBlock[hashes.Count];
-
- for (int i = 0; i < hashes.Count; i++)
- {
- cache.WearableData[i] = new AgentCachedTexturePacket.WearableDataBlock();
- cache.WearableData[i].TextureIndex = (byte)hashes[i].Key;
- cache.WearableData[i].ID = hashes[i].Value;
-
- Client.DebugLog("Checking cache for index " + cache.WearableData[i].TextureIndex +
- ", ID: " + cache.WearableData[i].ID);
- }
-
- // Increment our serial number for this packet
- CacheCheckSerialNum++;
-
- // Send it out
- Client.Network.SendPacket(cache);
- }
- }
-
- ///
- /// Ask the server what textures our avatar is currently wearing
- ///
- public void RequestAgentWearables()
- {
- AgentWearablesRequestPacket request = new AgentWearablesRequestPacket();
- request.AgentData.AgentID = Client.Network.AgentID;
- request.AgentData.SessionID = Client.Network.SessionID;
-
- Client.Network.SendPacket(request);
- }
-
- private void StartWear()
- {
- Client.DebugLog("StartWear()");
-
- DownloadWearables = true;
-
- // Register an asset download callback to get wearable data
- AssetManager.AssetReceivedCallback assetCallback = new AssetManager.AssetReceivedCallback(Assets_OnAssetReceived);
- AssetManager.ImageReceivedCallback imageCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived);
- AssetManager.AssetUploadedCallback uploadCallback = new AssetManager.AssetUploadedCallback(Assets_OnAssetUploaded);
- Assets.OnAssetReceived += assetCallback;
- Assets.OnImageReceived += imageCallback;
- Assets.OnAssetUploaded += uploadCallback;
-
- // Download assets for what we are wearing and fill in AgentTextures
- DownloadWearableAssets();
- WearablesDownloadedEvent.WaitOne();
-
- // Unregister the asset download callback
- Assets.OnAssetReceived -= assetCallback;
-
- string tex = "";
-
- for (int i = 0; i < AgentTextures.Length; i++)
- if (AgentTextures[i] != LLUUID.Zero)
- tex += ((TextureIndex)i).ToString() + " = " + AgentTextures[i] + "\n";
-
- Client.DebugLog("AgentTextures:\n" + tex);
-
- // Check if anything needs to be rebaked
- RequestCachedBakes();
-
- // Tell the sim what we are wearing
- SendAgentIsNowWearing();
-
- // Wait for cached layer check to finish
- CachedResponseEvent.WaitOne();
-
- // Unregister the image download and asset upload callbacks
- Assets.OnImageReceived -= imageCallback;
- Assets.OnAssetUploaded -= uploadCallback;
-
- Client.DebugLog("CachedResponseEvent completed");
-
- // Send all of the visual params and textures for our agent
- SendAgentSetAppearance();
- }
-
- private void StartSetPreviousAppearance()
- {
- DownloadWearables = true;
-
- // Register an asset download callback to get wearable data
- AssetManager.AssetReceivedCallback assetCallback = new AssetManager.AssetReceivedCallback(Assets_OnAssetReceived);
- AssetManager.ImageReceivedCallback imageCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived);
- AssetManager.AssetUploadedCallback uploadCallback = new AssetManager.AssetUploadedCallback(Assets_OnAssetUploaded);
- Assets.OnAssetReceived += assetCallback;
- Assets.OnImageReceived += imageCallback;
- Assets.OnAssetUploaded += uploadCallback;
-
- // Ask the server what we are currently wearing
- RequestAgentWearables();
-
- WearablesDownloadedEvent.WaitOne();
-
- // Unregister the asset download callback
- Assets.OnAssetReceived -= assetCallback;
-
- Client.DebugLog("WearablesDownloadEvent completed");
-
- // Now that we know what the avatar is wearing, we can check if anything needs to be rebaked
- RequestCachedBakes();
-
- CachedResponseEvent.WaitOne();
-
- // Send a list of what we are currently wearing
- SendAgentIsNowWearing();
-
- // Unregister the image download and asset upload callbacks
- Assets.OnImageReceived -= imageCallback;
- Assets.OnAssetUploaded -= uploadCallback;
-
- Client.DebugLog("CachedResponseEvent completed");
-
- // Send all of the visual params and textures for our agent
- SendAgentSetAppearance();
- }
-
- private void SendAgentSetAppearance()
- {
- AgentSetAppearancePacket set = new AgentSetAppearancePacket();
- set.AgentData.AgentID = Client.Network.AgentID;
- set.AgentData.SessionID = Client.Network.SessionID;
- set.AgentData.SerialNum = SetAppearanceSerialNum++;
- set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[VisualParams.Params.Count];
-
- lock (Wearables)
- {
- // Only for debugging output
- int count = 0, vpIndex = 0;
-
- // Build the visual param array
- foreach (KeyValuePair kvp in VisualParams.Params)
- {
- bool found = false;
- set.VisualParam[vpIndex] = new AgentSetAppearancePacket.VisualParamBlock();
- VisualParam vp = kvp.Value;
-
- // Try and find this value in our collection of downloaded wearables
- foreach (WearableData data in Wearables.Values)
- {
- if (data.Wearable.Params.ContainsKey(vp.ParamID))
- {
- set.VisualParam[vpIndex].ParamValue = Helpers.FloatToByte(data.Wearable.Params[vp.ParamID],
- vp.MinValue, vp.MaxValue);
- found = true;
- count++;
- break;
- }
- }
-
- // Use a default value if we don't have one set for it
- if (!found)
- {
- set.VisualParam[vpIndex].ParamValue = Helpers.FloatToByte(vp.DefaultValue,
- vp.MinValue, vp.MaxValue);
- }
-
- vpIndex++;
- }
-
- Client.DebugLog("AgentSetAppearance contains " + count + " VisualParams");
-
- // Build the texture entry for our agent
- LLObject.TextureEntry te = new LLObject.TextureEntry(DEFAULT_AVATAR_TEXTURE);
-
- // Put our AgentTextures array in to TextureEntry
- lock (AgentTextures)
- {
- for (uint i = 0; i < AgentTextures.Length; i++)
- {
- if (AgentTextures[i] != LLUUID.Zero)
- {
- LLObject.TextureEntryFace face = te.CreateFace(i);
- face.TextureID = AgentTextures[i];
- }
- }
- }
-
- foreach (WearableData data in Wearables.Values)
- {
- foreach (KeyValuePair texture in data.Wearable.Textures)
- {
- LLObject.TextureEntryFace face = te.CreateFace((uint)texture.Key);
- face.TextureID = texture.Value;
-
- Client.DebugLog("Setting texture " + ((TextureIndex)texture.Key).ToString() + " to " +
- texture.Value.ToStringHyphenated());
- }
- }
-
- // Set the packet TextureEntry
- set.ObjectData.TextureEntry = te.ToBytes();
- }
-
- // FIXME: Our hackish algorithm is making squished avatars. See
- // http://www.libsecondlife.org/wiki/Agent_Size for discussion of the correct algorithm
- float height = Helpers.ByteToFloat(set.VisualParam[33].ParamValue, VisualParams.Params[33].MinValue,
- VisualParams.Params[33].MaxValue);
- set.AgentData.Size = new LLVector3(0.45f, 0.6f, 1.50856f + ((height / 255.0f) * (2.025506f - 1.50856f)));
-
- // TODO: Account for not having all the textures baked yet
- set.WearableData = new AgentSetAppearancePacket.WearableDataBlock[BAKED_TEXTURE_COUNT];
-
- // Build hashes for each of the bake layers from the individual components
- for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
- {
- LLUUID hash = new LLUUID();
-
- for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
- {
- Wearable.WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
- LLUUID assetID = GetWearableAsset(type);
-
- // Build a hash of all the texture asset IDs in this baking layer
- if (assetID != LLUUID.Zero) hash ^= assetID;
- }
-
- if (hash != LLUUID.Zero)
- {
- // Hash with our secret value for this baked layer
- hash ^= BAKED_TEXTURE_HASH[bakedIndex];
- }
-
- // Tell the server what cached texture assetID to use for each bake layer
- set.WearableData[bakedIndex] = new AgentSetAppearancePacket.WearableDataBlock();
- set.WearableData[bakedIndex].TextureIndex = (byte)bakedIndex;
- set.WearableData[bakedIndex].CacheID = hash;
- }
-
- // Finally, send the packet
- Client.Network.SendPacket(set);
- }
-
- private void SendAgentIsNowWearing()
- {
- Client.DebugLog("SendAgentIsNowWearing()");
-
- AgentIsNowWearingPacket wearing = new AgentIsNowWearingPacket();
- wearing.AgentData.AgentID = Client.Network.AgentID;
- wearing.AgentData.SessionID = Client.Network.SessionID;
- wearing.WearableData = new AgentIsNowWearingPacket.WearableDataBlock[WEARABLE_COUNT];
-
- for (int i = 0; i < WEARABLE_COUNT; i++)
- {
- Wearable.WearableType type = (Wearable.WearableType)i;
- wearing.WearableData[i] = new AgentIsNowWearingPacket.WearableDataBlock();
- wearing.WearableData[i].WearableType = (byte)i;
-
- if (Wearables.ContainsKey(type))
- wearing.WearableData[i].ItemID = Wearables[type].ItemID;
- else
- wearing.WearableData[i].ItemID = LLUUID.Zero;
- }
-
- Client.Network.SendPacket(wearing);
- }
-
- private TextureIndex BakeTypeToAgentTextureIndex(BakeType index)
- {
- switch (index)
- {
- case BakeType.Head:
- return TextureIndex.HeadBaked;
- case BakeType.UpperBody:
- return TextureIndex.UpperBaked;
- case BakeType.LowerBody:
- return TextureIndex.LowerBaked;
- case BakeType.Eyes:
- return TextureIndex.EyesBaked;
- case BakeType.Skirt:
- return TextureIndex.SkirtBaked;
- default:
- return TextureIndex.Unknown;
- }
- }
-
- private void DownloadWearableAssets()
- {
- Client.DebugLog("DownloadWearableAssets()");
-
- foreach (KeyValuePair kvp in Wearables)
- DownloadQueue.Enqueue(new PendingAssetDownload(kvp.Value.AssetID,Wearable.WearableTypeToAssetType(kvp.Value.Wearable.Type)));
-
- if (DownloadQueue.Count > 0)
- {
- PendingAssetDownload download = DownloadQueue.Dequeue();
- Assets.RequestAsset(download.Id, download.Type, true);
- }
- }
-
- private void AgentWearablesHandler(Packet packet, Simulator simulator)
- {
- // Lock to prevent a race condition with multiple AgentWearables packets
- lock (WearablesDownloadedEvent)
- {
- AgentWearablesUpdatePacket update = (AgentWearablesUpdatePacket)packet;
-
- // Reset the Wearables collection
- lock (Wearables) Wearables.Clear();
-
- for (int i = 0; i < update.WearableData.Length; i++)
- {
- if (update.WearableData[i].AssetID != LLUUID.Zero)
- {
- Wearable.WearableType type = (Wearable.WearableType)update.WearableData[i].WearableType;
- WearableData data = new WearableData();
- data.AssetID = update.WearableData[i].AssetID;
- data.ItemID = update.WearableData[i].ItemID;
- data.Wearable = new Wearable(Client);
- data.Wearable.Type = type;
-
- // Add this wearable to our collection
- lock (Wearables) Wearables[type] = data;
-
- // Convert WearableType to AssetType
- AssetType assetType = Wearable.WearableTypeToAssetType(type);
-
- Client.DebugLog("Downloading wearable " + type.ToString() + ": " +
- data.AssetID.ToStringHyphenated());
-
- // Add this wearable asset to the download queue
- if (DownloadWearables)
- {
- PendingAssetDownload download = new PendingAssetDownload(data.AssetID, assetType);
- DownloadQueue.Enqueue(download);
- }
- }
- }
-
- if (DownloadQueue.Count > 0)
- {
- PendingAssetDownload download = DownloadQueue.Dequeue();
- Assets.RequestAsset(download.Id, download.Type, true);
- }
-
- // Don't download wearables twice in a row
- DownloadWearables = false;
- }
-
- CallOnAgentWearables();
- }
-
- private void CallOnAgentWearables()
- {
- if (OnAgentWearables != null)
- {
- // Refactor our internal Wearables dictionary in to something for the callback
- Dictionary> wearables =
- new Dictionary>();
-
- lock (Wearables)
- {
- foreach (KeyValuePair data in Wearables)
- wearables.Add(data.Key, new KeyValuePair(data.Value.AssetID, data.Value.ItemID));
- }
-
- try { OnAgentWearables(wearables); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
- }
- }
-
- private void AgentCachedTextureResponseHandler(Packet packet, Simulator simulator)
- {
- Client.DebugLog("AgentCachedTextureResponseHandler()");
-
- AgentCachedTextureResponsePacket response = (AgentCachedTextureResponsePacket)packet;
- Dictionary paramValues = new Dictionary(VisualParams.Params.Count);
-
- // Build a dictionary of appearance parameter indices and values from the wearables
- foreach (KeyValuePair kvp in VisualParams.Params)
- {
- bool found = false;
- VisualParam vp = kvp.Value;
-
- // Try and find this value in our collection of downloaded wearables
- foreach (WearableData data in Wearables.Values)
- {
- if (data.Wearable.Params.ContainsKey(vp.ParamID))
- {
- paramValues.Add(vp.ParamID,data.Wearable.Params[vp.ParamID]);
- found = true;
- break;
- }
- }
-
- // Use a default value if we don't have one set for it
- if (!found) paramValues.Add(vp.ParamID, vp.DefaultValue);
- }
-
- lock (AgentTextures)
- {
- foreach (AgentCachedTextureResponsePacket.WearableDataBlock block in response.WearableData)
- {
- // For each missing element we need to bake our own texture
- Client.DebugLog("Cache response, index: " + block.TextureIndex + ", ID: " +
- block.TextureID.ToStringHyphenated());
-
- // FIXME: Use this. Right now we treat baked images on other sims as if they were missing
- string host = Helpers.FieldToUTF8String(block.HostName);
- if (host.Length > 0) Client.DebugLog("Cached bake exists on foreign host " + host);
-
- BakeType bakeType = (BakeType)block.TextureIndex;
-
- // Convert the baked index to an AgentTexture index
- if (block.TextureID != LLUUID.Zero && host.Length == 0)
- {
- TextureIndex index = BakeTypeToAgentTextureIndex(bakeType);
- AgentTextures[(int)index] = block.TextureID;
- }
- else
- {
- int imageCount = 0;
-
- // Download all of the images in this layer
- switch (bakeType)
- {
- case BakeType.Head:
- lock (ImageDownloads)
- {
- imageCount += AddImageDownload(TextureIndex.HeadBodypaint);
- //imageCount += AddImageDownload(TextureIndex.Hair);
- }
- break;
- case BakeType.UpperBody:
- lock (ImageDownloads)
- {
- imageCount += AddImageDownload(TextureIndex.UpperBodypaint);
- imageCount += AddImageDownload(TextureIndex.UpperGloves);
- imageCount += AddImageDownload(TextureIndex.UpperUndershirt);
- imageCount += AddImageDownload(TextureIndex.UpperShirt);
- imageCount += AddImageDownload(TextureIndex.UpperJacket);
- }
- break;
- case BakeType.LowerBody:
- lock (ImageDownloads)
- {
- imageCount += AddImageDownload(TextureIndex.LowerBodypaint);
- imageCount += AddImageDownload(TextureIndex.LowerUnderpants);
- imageCount += AddImageDownload(TextureIndex.LowerSocks);
- imageCount += AddImageDownload(TextureIndex.LowerShoes);
- imageCount += AddImageDownload(TextureIndex.LowerPants);
- imageCount += AddImageDownload(TextureIndex.LowerJacket);
- }
- break;
- case BakeType.Eyes:
- lock (ImageDownloads)
- {
- imageCount += AddImageDownload(TextureIndex.EyesIris);
- }
- break;
- case BakeType.Skirt:
- if (Wearables.ContainsKey(Wearable.WearableType.Skirt))
- {
- lock (ImageDownloads)
- {
- imageCount += AddImageDownload(TextureIndex.Skirt);
- }
- }
- break;
- default:
- Client.Log("Unknown BakeType " + block.TextureIndex, Helpers.LogLevel.Warning);
- break;
- }
-
- if (!PendingBakes.ContainsKey(bakeType))
- {
- Client.DebugLog("Initializing " + bakeType.ToString() + " bake with " + imageCount + " textures");
-
- if (imageCount == 0)
- {
- // if there are no textures to download, we can bake right away and start the upload
- Baker bake = new Baker(Client, bakeType, 0, paramValues);
- UploadBake(bake);
- }
- else
- {
- lock (PendingBakes)
- PendingBakes.Add(bakeType, new Baker(Client, bakeType, imageCount, paramValues));
- }
- }
- else if (!PendingBakes.ContainsKey(bakeType))
- {
- Client.Log("No cached bake for " + bakeType.ToString() + " and no textures for that " +
- "layer, this is an unhandled case", Helpers.LogLevel.Error);
- }
- }
- }
- }
-
- if (ImageDownloads.Count == 0)
- {
- // No pending downloads for baking, we're done
- CachedResponseEvent.Set();
- }
- else
- {
- lock (ImageDownloads)
- {
- foreach (LLUUID image in ImageDownloads.Keys)
- {
- // Download all the images we need for baking
- Assets.RequestImage(image, ImageType.Normal, 1013000.0f, 0);
- }
- }
- }
- }
-
- private int AddImageDownload(TextureIndex index)
- {
- LLUUID image = AgentTextures[(int)index];
-
- if (image != LLUUID.Zero)
- {
- if (!ImageDownloads.ContainsKey(image))
- {
- Client.DebugLog("Downloading layer " + index.ToString());
- ImageDownloads.Add(image, index);
- }
-
- return 1;
- }
-
- return 0;
- }
-
- private void Assets_OnAssetReceived(AssetDownload asset)
- {
- Client.DebugLog("Assets_OnAssetReceived()");
-
- lock (Wearables)
- {
- // Check if this is a wearable we were waiting on
- foreach (WearableData data in Wearables.Values)
- {
- if (data.AssetID == asset.AssetID)
- {
- // Make sure the download succeeded
- if (asset.Success)
- {
- // Convert the downloaded asset to a string
- string wearableData = Helpers.FieldToUTF8String(asset.AssetData);
-
- // Attempt to parse the wearable data
- if (data.Wearable.ImportAsset(wearableData))
- {
- Client.DebugLog("Imported wearable asset " + data.Wearable.Type.ToString());
- Client.DebugLog(wearableData);
-
- lock (AgentTextures)
- {
- foreach (KeyValuePair texture in data.Wearable.Textures)
- {
- Client.DebugLog("Setting " + texture.Key + " to " + texture.Value);
- AgentTextures[(int)texture.Key] = texture.Value;
- }
- }
- }
- else
- {
- Client.Log("Failed to decode wearable asset " + asset.AssetID.ToStringHyphenated(),
- Helpers.LogLevel.Warning);
- }
- }
- else
- {
- Client.Log("Wearable " + data.Wearable.Type.ToString() + "(" +
- asset.AssetID.ToStringHyphenated() + ") failed to download, " + asset.Status.ToString(),
- Helpers.LogLevel.Warning);
- }
-
- break;
- }
- }
- }
-
- if (DownloadQueue.Count > 0)
- {
- // Dowload the next wearable in line
- PendingAssetDownload download = DownloadQueue.Dequeue();
- Assets.RequestAsset(download.Id, download.Type, true);
- }
- else
- {
- // Everything is downloaded
- WearablesDownloadedEvent.Set();
- }
- }
-
- private void Assets_OnImageReceived(ImageDownload image)
- {
- lock (ImageDownloads)
- {
- if (ImageDownloads.ContainsKey(image.ID))
- {
- // NOTE: this image may occupy more than one TextureIndex! We must finish this loop
- for (int at = 0; at < AgentTextures.Length; at++)
- {
- if (AgentTextures[at] == image.ID)
- {
- TextureIndex index = (TextureIndex)at;
- Client.DebugLog("Finished downloading texture for " + index.ToString());
- BakeType type = Baker.BakeTypeFor(index);
-
- //BinaryWriter writer = new BinaryWriter(File.Create("wearable_" + index.ToString() + "_" + image.ID.ToString() + ".jp2"));
- //writer.Write(image.AssetData);
- //writer.Close();
-
- bool baked = false;
-
- if (PendingBakes.ContainsKey(type))
- {
- if (image.Success)
- baked = PendingBakes[type].AddTexture(index, image.AssetData);
- else
- {
- Client.Log("Texture for " + index.ToString() + " failed to download, " +
- "bake will be incomplete", Helpers.LogLevel.Warning);
-
- baked = PendingBakes[type].MissingTexture(index);
- }
- }
-
- if (baked)
- {
- UploadBake(PendingBakes[type]);
- PendingBakes.Remove(type);
- }
-
- ImageDownloads.Remove(image.ID);
-
- if (ImageDownloads.Count == 0 && PendingUploads.Count == 0)
- {
- // This is a failsafe catch, as the upload completed callback should normally
- // be triggering the event
- Client.DebugLog("No pending downloads or uploads detected in OnImageReceived");
- CachedResponseEvent.Set();
- }
- else
- {
- Client.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
- ImageDownloads.Count);
- }
-
- }
- }
- }
- else
- Client.Log("Received an image download callback for an image we did not request " + image.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
- }
- }
-
- private void UploadBake(Baker bake)
- {
- // Create a transactionID and assetID for this upload
- LLUUID transactionID = LLUUID.Random();
- LLUUID assetID = transactionID.Combine(Client.Network.SecureSessionID);
-
- Client.DebugLog("Bake " + bake.BakeType.ToString() + " completed. Uploading asset " + assetID.ToStringHyphenated());
-
- // Upload the completed layer data
- Assets.RequestUpload(transactionID, AssetType.Texture, bake.EncodedBake, true, true, false);
-
- // Add it to a pending uploads list
- lock (PendingUploads) PendingUploads.Add(assetID, BakeTypeToAgentTextureIndex(bake.BakeType));
- }
-
- private void Assets_OnAssetUploaded(AssetUpload upload)
- {
- lock (PendingUploads)
- {
- if (PendingUploads.ContainsKey(upload.AssetID))
- {
- if (upload.Success)
- {
- // Setup the TextureEntry with the new baked upload
- TextureIndex index = PendingUploads[upload.AssetID];
- AgentTextures[(int)index] = upload.AssetID;
-
- Client.DebugLog("Upload complete, AgentTextures " + index.ToString() + " set to " +
- upload.AssetID.ToStringHyphenated());
- }
- else
- {
- Client.Log("Asset upload " + upload.AssetID.ToStringHyphenated() + " failed",
- Helpers.LogLevel.Warning);
- }
-
- PendingUploads.Remove(upload.AssetID);
-
- Client.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
- ImageDownloads.Count);
-
- if (PendingUploads.Count == 0 && ImageDownloads.Count == 0)
- {
- Client.DebugLog("All pending image downloads and uploads complete");
-
- CachedResponseEvent.Set();
- }
- }
- else
- {
- // TEMP
- Client.DebugLog("Upload " + upload.AssetID.ToStringHyphenated() + " was not found in PendingUploads");
- }
- }
- }
- }
-}
+ private Queue DownloadQueue = new Queue();
+ // A list of all the images we are currently downloading, prior to baking
+ private Dictionary ImageDownloads = new Dictionary();
+ // A list of all the bakes we need to complete
+ private Dictionary PendingBakes = new Dictionary(BAKED_TEXTURE_COUNT);
+ // A list of all the uploads that are in progress
+ private Dictionary PendingUploads = new Dictionary(BAKED_TEXTURE_COUNT);
+ // Whether the handler for our current wearable list should automatically start downloading the assets
+ private bool DownloadWearables = false;
+ private static int CacheCheckSerialNum = 1; //FIXME
+ private static uint SetAppearanceSerialNum = 1; //FIXME
+ private AutoResetEvent WearablesDownloadedEvent = new AutoResetEvent(false);
+ private AutoResetEvent CachedResponseEvent = new AutoResetEvent(false);
+ // FIXME: Create a class-level appearance thread so multiple threads can't be launched
+
+ ///
+ /// Default constructor
+ ///
+ ///
+ ///
+ public AppearanceManager(SecondLife client, AssetManager assets)
+ {
+ Client = client;
+ Assets = assets;
+
+ // Initialize AgentTextures to zero UUIDs
+ for (int i = 0; i < AgentTextures.Length; i++)
+ AgentTextures[i] = LLUUID.Zero;
+
+ Client.Network.RegisterCallback(PacketType.AgentWearablesUpdate, new NetworkManager.PacketCallback(AgentWearablesHandler));
+ Client.Network.RegisterCallback(PacketType.AgentCachedTextureResponse, new NetworkManager.PacketCallback(AgentCachedTextureResponseHandler));
+ }
+
+ ///
+ /// If the appearance thread is running it is terminated here
+ ///
+ ~AppearanceManager()
+ {
+ WearablesDownloadedEvent.Set();
+ CachedResponseEvent.Set();
+ }
+
+ ///
+ /// Returns the assetID for a given WearableType
+ ///
+ ///
+ ///
+ public LLUUID GetWearableAsset(Wearable.WearableType type)
+ {
+ if (Wearables.ContainsKey(type))
+ return Wearables[type].AssetID;
+ else
+ return LLUUID.Zero;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void SetPreviousAppearance()
+ {
+ // Clear out any previous data
+ DownloadWearables = false;
+ lock (Wearables) Wearables.Clear();
+ lock (AgentTextures)
+ {
+ for (int i = 0; i < AgentTextures.Length; i++)
+ AgentTextures[i] = LLUUID.Zero;
+ }
+ lock (DownloadQueue) DownloadQueue.Clear();
+
+ Thread appearanceThread = new Thread(new ThreadStart(StartSetPreviousAppearance));
+ appearanceThread.Start();
+ }
+
+ ///
+ /// Add a single wearable to your outfit, replacing if nessesary.
+ ///
+ ///
+ /*public void Wear(libsecondlife.InventorySystem.InventoryWearable wearable)
+ {
+ List x = new List();
+ x.Add(wearable);
+ Wear(x);
+ }
+
+ public void Wear(List iws)
+ {
+ DownloadWearables = false;
+
+ lock (Wearables)
+ {
+ Dictionary preserve = new Dictionary(4);
+
+ foreach (KeyValuePair kvp in Wearables)
+ {
+ if (
+ kvp.Key == Wearable.WearableType.Shape ||
+ kvp.Key == Wearable.WearableType.Skin ||
+ kvp.Key == Wearable.WearableType.Eyes ||
+ kvp.Key == Wearable.WearableType.Hair
+ )
+ {
+ preserve.Add(kvp.Key,kvp.Value);
+ Client.DebugLog("Keeping " + kvp.Key.ToString() + " " + kvp.Value.Wearable.Name);
+ }
+ }
+
+ Wearables = preserve;
+
+ foreach (libsecondlife.InventorySystem.InventoryWearable iw in iws)
+ {
+ WearableData wd = WearableData.FromInventoryWearable(Client,iw);
+ Wearables[wd.Wearable.Type] = wd;
+ Client.DebugLog("Found " + iw.Name);
+ }
+ }
+
+ lock (DownloadQueue) DownloadQueue.Clear();
+ lock (ImageDownloads) ImageDownloads.Clear();
+ lock (PendingBakes) PendingBakes.Clear();
+ lock (PendingUploads) PendingUploads.Clear();
+
+ lock (AgentTextures)
+ {
+ for (int i = 0; i < AgentTextures.Length; i++)
+ AgentTextures[i] = LLUUID.Zero;
+ }
+
+ Thread appearanceThread = new Thread(new ThreadStart(StartWear));
+ appearanceThread.Start();
+ }
+
+ public void WearOutfit(InventoryFolder folder)
+ {
+ Thread wearOutfitThread = new Thread(new ParameterizedThreadStart(WearOutfitAsync));
+ wearOutfitThread.Start(folder);
+ }
+
+ public void WearOutfit(string folder)
+ {
+ Thread wearOutfitThread = new Thread(new ParameterizedThreadStart(WearOutfitAsync));
+ wearOutfitThread.Start(folder);
+ }
+
+ public void WearOutfitAsync(object _folder)
+ {
+ InventoryFolder folder;
+
+ if (_folder is string)
+ folder = Client.Inventory.getFolder((string)_folder);
+ else
+ folder = (InventoryFolder)_folder;
+
+ List iws = new List();
+ folder.RequestDownloadContents(false, false, true).RequestComplete.WaitOne();
+
+ foreach (InventoryBase ib in folder.GetContents())
+ {
+ if (ib is InventoryWearable)
+ iws.Add((InventoryWearable)ib);
+ }
+
+ Wear(iws);
+ }*/
+
+ ///
+ /// Build hashes out of the texture assetIDs for each baking layer to
+ /// ask the simulator whether it has cached copies of each baked texture
+ ///
+ public void RequestCachedBakes()
+ {
+ Client.DebugLog("RequestCachedBakes()");
+
+ List> hashes = new List>();
+
+ AgentCachedTexturePacket cache = new AgentCachedTexturePacket();
+ cache.AgentData.AgentID = Client.Network.AgentID;
+ cache.AgentData.SessionID = Client.Network.SessionID;
+ cache.AgentData.SerialNum = CacheCheckSerialNum;
+
+ // Build hashes for each of the bake layers from the individual components
+ for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
+ {
+ // Don't do a cache request for a skirt bake if we're not wearing a skirt
+ if (bakedIndex == (int)BakeType.Skirt &&
+ (!Wearables.ContainsKey(Wearable.WearableType.Skirt) || Wearables[Wearable.WearableType.Skirt].AssetID == LLUUID.Zero))
+ continue;
+
+ LLUUID hash = new LLUUID();
+
+ for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
+ {
+ Wearable.WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
+ LLUUID assetID = GetWearableAsset(type);
+
+ // Build a hash of all the texture asset IDs in this baking layer
+ if (assetID != null) hash ^= assetID;
+ }
+
+ if (hash != LLUUID.Zero)
+ {
+ // Hash with our secret value for this baked layer
+ hash ^= BAKED_TEXTURE_HASH[bakedIndex];
+
+ // Add this to the list of hashes to send out
+ hashes.Add(new KeyValuePair(bakedIndex, hash));
+ }
+ }
+
+ // Only send the packet out if there's something to check
+ if (hashes.Count > 0)
+ {
+ cache.WearableData = new AgentCachedTexturePacket.WearableDataBlock[hashes.Count];
+
+ for (int i = 0; i < hashes.Count; i++)
+ {
+ cache.WearableData[i] = new AgentCachedTexturePacket.WearableDataBlock();
+ cache.WearableData[i].TextureIndex = (byte)hashes[i].Key;
+ cache.WearableData[i].ID = hashes[i].Value;
+
+ Client.DebugLog("Checking cache for index " + cache.WearableData[i].TextureIndex +
+ ", ID: " + cache.WearableData[i].ID);
+ }
+
+ // Increment our serial number for this packet
+ CacheCheckSerialNum++;
+
+ // Send it out
+ Client.Network.SendPacket(cache);
+ }
+ }
+
+ ///
+ /// Ask the server what textures our avatar is currently wearing
+ ///
+ public void RequestAgentWearables()
+ {
+ AgentWearablesRequestPacket request = new AgentWearablesRequestPacket();
+ request.AgentData.AgentID = Client.Network.AgentID;
+ request.AgentData.SessionID = Client.Network.SessionID;
+
+ Client.Network.SendPacket(request);
+ }
+
+ private void StartWear()
+ {
+ Client.DebugLog("StartWear()");
+
+ DownloadWearables = true;
+
+ // Register an asset download callback to get wearable data
+ AssetManager.AssetReceivedCallback assetCallback = new AssetManager.AssetReceivedCallback(Assets_OnAssetReceived);
+ AssetManager.ImageReceivedCallback imageCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived);
+ AssetManager.AssetUploadedCallback uploadCallback = new AssetManager.AssetUploadedCallback(Assets_OnAssetUploaded);
+ Assets.OnAssetReceived += assetCallback;
+ Assets.OnImageReceived += imageCallback;
+ Assets.OnAssetUploaded += uploadCallback;
+
+ // Download assets for what we are wearing and fill in AgentTextures
+ DownloadWearableAssets();
+ WearablesDownloadedEvent.WaitOne();
+
+ // Unregister the asset download callback
+ Assets.OnAssetReceived -= assetCallback;
+
+ string tex = "";
+
+ for (int i = 0; i < AgentTextures.Length; i++)
+ if (AgentTextures[i] != LLUUID.Zero)
+ tex += ((TextureIndex)i).ToString() + " = " + AgentTextures[i] + "\n";
+
+ Client.DebugLog("AgentTextures:\n" + tex);
+
+ // Check if anything needs to be rebaked
+ RequestCachedBakes();
+
+ // Tell the sim what we are wearing
+ SendAgentIsNowWearing();
+
+ // Wait for cached layer check to finish
+ CachedResponseEvent.WaitOne();
+
+ // Unregister the image download and asset upload callbacks
+ Assets.OnImageReceived -= imageCallback;
+ Assets.OnAssetUploaded -= uploadCallback;
+
+ Client.DebugLog("CachedResponseEvent completed");
+
+ // Send all of the visual params and textures for our agent
+ SendAgentSetAppearance();
+ }
+
+ private void StartSetPreviousAppearance()
+ {
+ DownloadWearables = true;
+
+ // Register an asset download callback to get wearable data
+ AssetManager.AssetReceivedCallback assetCallback = new AssetManager.AssetReceivedCallback(Assets_OnAssetReceived);
+ AssetManager.ImageReceivedCallback imageCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived);
+ AssetManager.AssetUploadedCallback uploadCallback = new AssetManager.AssetUploadedCallback(Assets_OnAssetUploaded);
+ Assets.OnAssetReceived += assetCallback;
+ Assets.OnImageReceived += imageCallback;
+ Assets.OnAssetUploaded += uploadCallback;
+
+ // Ask the server what we are currently wearing
+ RequestAgentWearables();
+
+ WearablesDownloadedEvent.WaitOne();
+
+ // Unregister the asset download callback
+ Assets.OnAssetReceived -= assetCallback;
+
+ Client.DebugLog("WearablesDownloadEvent completed");
+
+ // Now that we know what the avatar is wearing, we can check if anything needs to be rebaked
+ RequestCachedBakes();
+
+ CachedResponseEvent.WaitOne();
+
+ // Send a list of what we are currently wearing
+ SendAgentIsNowWearing();
+
+ // Unregister the image download and asset upload callbacks
+ Assets.OnImageReceived -= imageCallback;
+ Assets.OnAssetUploaded -= uploadCallback;
+
+ Client.DebugLog("CachedResponseEvent completed");
+
+ // Send all of the visual params and textures for our agent
+ SendAgentSetAppearance();
+ }
+
+ private void SendAgentSetAppearance()
+ {
+ AgentSetAppearancePacket set = new AgentSetAppearancePacket();
+ set.AgentData.AgentID = Client.Network.AgentID;
+ set.AgentData.SessionID = Client.Network.SessionID;
+ set.AgentData.SerialNum = SetAppearanceSerialNum++;
+ set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[VisualParams.Params.Count];
+
+ lock (Wearables)
+ {
+ // Only for debugging output
+ int count = 0, vpIndex = 0;
+
+ // Build the visual param array
+ foreach (KeyValuePair kvp in VisualParams.Params)
+ {
+ bool found = false;
+ set.VisualParam[vpIndex] = new AgentSetAppearancePacket.VisualParamBlock();
+ VisualParam vp = kvp.Value;
+
+ // Try and find this value in our collection of downloaded wearables
+ foreach (WearableData data in Wearables.Values)
+ {
+ if (data.Wearable.Params.ContainsKey(vp.ParamID))
+ {
+ set.VisualParam[vpIndex].ParamValue = Helpers.FloatToByte(data.Wearable.Params[vp.ParamID],
+ vp.MinValue, vp.MaxValue);
+ found = true;
+ count++;
+ break;
+ }
+ }
+
+ // Use a default value if we don't have one set for it
+ if (!found)
+ {
+ set.VisualParam[vpIndex].ParamValue = Helpers.FloatToByte(vp.DefaultValue,
+ vp.MinValue, vp.MaxValue);
+ }
+
+ vpIndex++;
+ }
+
+ Client.DebugLog("AgentSetAppearance contains " + count + " VisualParams");
+
+ // Build the texture entry for our agent
+ LLObject.TextureEntry te = new LLObject.TextureEntry(DEFAULT_AVATAR_TEXTURE);
+
+ // Put our AgentTextures array in to TextureEntry
+ lock (AgentTextures)
+ {
+ for (uint i = 0; i < AgentTextures.Length; i++)
+ {
+ if (AgentTextures[i] != LLUUID.Zero)
+ {
+ LLObject.TextureEntryFace face = te.CreateFace(i);
+ face.TextureID = AgentTextures[i];
+ }
+ }
+ }
+
+ foreach (WearableData data in Wearables.Values)
+ {
+ foreach (KeyValuePair texture in data.Wearable.Textures)
+ {
+ LLObject.TextureEntryFace face = te.CreateFace((uint)texture.Key);
+ face.TextureID = texture.Value;
+
+ Client.DebugLog("Setting texture " + ((TextureIndex)texture.Key).ToString() + " to " +
+ texture.Value.ToStringHyphenated());
+ }
+ }
+
+ // Set the packet TextureEntry
+ set.ObjectData.TextureEntry = te.ToBytes();
+ }
+
+ // FIXME: Our hackish algorithm is making squished avatars. See
+ // http://www.libsecondlife.org/wiki/Agent_Size for discussion of the correct algorithm
+ float height = Helpers.ByteToFloat(set.VisualParam[33].ParamValue, VisualParams.Params[33].MinValue,
+ VisualParams.Params[33].MaxValue);
+ set.AgentData.Size = new LLVector3(0.45f, 0.6f, 1.50856f + ((height / 255.0f) * (2.025506f - 1.50856f)));
+
+ // TODO: Account for not having all the textures baked yet
+ set.WearableData = new AgentSetAppearancePacket.WearableDataBlock[BAKED_TEXTURE_COUNT];
+
+ // Build hashes for each of the bake layers from the individual components
+ for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
+ {
+ LLUUID hash = new LLUUID();
+
+ for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
+ {
+ Wearable.WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
+ LLUUID assetID = GetWearableAsset(type);
+
+ // Build a hash of all the texture asset IDs in this baking layer
+ if (assetID != LLUUID.Zero) hash ^= assetID;
+ }
+
+ if (hash != LLUUID.Zero)
+ {
+ // Hash with our secret value for this baked layer
+ hash ^= BAKED_TEXTURE_HASH[bakedIndex];
+ }
+
+ // Tell the server what cached texture assetID to use for each bake layer
+ set.WearableData[bakedIndex] = new AgentSetAppearancePacket.WearableDataBlock();
+ set.WearableData[bakedIndex].TextureIndex = (byte)bakedIndex;
+ set.WearableData[bakedIndex].CacheID = hash;
+ }
+
+ // Finally, send the packet
+ Client.Network.SendPacket(set);
+ }
+
+ private void SendAgentIsNowWearing()
+ {
+ Client.DebugLog("SendAgentIsNowWearing()");
+
+ AgentIsNowWearingPacket wearing = new AgentIsNowWearingPacket();
+ wearing.AgentData.AgentID = Client.Network.AgentID;
+ wearing.AgentData.SessionID = Client.Network.SessionID;
+ wearing.WearableData = new AgentIsNowWearingPacket.WearableDataBlock[WEARABLE_COUNT];
+
+ for (int i = 0; i < WEARABLE_COUNT; i++)
+ {
+ Wearable.WearableType type = (Wearable.WearableType)i;
+ wearing.WearableData[i] = new AgentIsNowWearingPacket.WearableDataBlock();
+ wearing.WearableData[i].WearableType = (byte)i;
+
+ if (Wearables.ContainsKey(type))
+ wearing.WearableData[i].ItemID = Wearables[type].ItemID;
+ else
+ wearing.WearableData[i].ItemID = LLUUID.Zero;
+ }
+
+ Client.Network.SendPacket(wearing);
+ }
+
+ private TextureIndex BakeTypeToAgentTextureIndex(BakeType index)
+ {
+ switch (index)
+ {
+ case BakeType.Head:
+ return TextureIndex.HeadBaked;
+ case BakeType.UpperBody:
+ return TextureIndex.UpperBaked;
+ case BakeType.LowerBody:
+ return TextureIndex.LowerBaked;
+ case BakeType.Eyes:
+ return TextureIndex.EyesBaked;
+ case BakeType.Skirt:
+ return TextureIndex.SkirtBaked;
+ default:
+ return TextureIndex.Unknown;
+ }
+ }
+
+ private void DownloadWearableAssets()
+ {
+ Client.DebugLog("DownloadWearableAssets()");
+
+ foreach (KeyValuePair kvp in Wearables)
+ DownloadQueue.Enqueue(new PendingAssetDownload(kvp.Value.AssetID,Wearable.WearableTypeToAssetType(kvp.Value.Wearable.Type)));
+
+ if (DownloadQueue.Count > 0)
+ {
+ PendingAssetDownload download = DownloadQueue.Dequeue();
+ Assets.RequestAsset(download.Id, download.Type, true);
+ }
+ }
+
+ private void AgentWearablesHandler(Packet packet, Simulator simulator)
+ {
+ // Lock to prevent a race condition with multiple AgentWearables packets
+ lock (WearablesDownloadedEvent)
+ {
+ AgentWearablesUpdatePacket update = (AgentWearablesUpdatePacket)packet;
+
+ // Reset the Wearables collection
+ lock (Wearables) Wearables.Clear();
+
+ for (int i = 0; i < update.WearableData.Length; i++)
+ {
+ if (update.WearableData[i].AssetID != LLUUID.Zero)
+ {
+ Wearable.WearableType type = (Wearable.WearableType)update.WearableData[i].WearableType;
+ WearableData data = new WearableData();
+ data.AssetID = update.WearableData[i].AssetID;
+ data.ItemID = update.WearableData[i].ItemID;
+ data.Wearable = new Wearable(Client);
+ data.Wearable.Type = type;
+
+ // Add this wearable to our collection
+ lock (Wearables) Wearables[type] = data;
+
+ // Convert WearableType to AssetType
+ AssetType assetType = Wearable.WearableTypeToAssetType(type);
+
+ Client.DebugLog("Downloading wearable " + type.ToString() + ": " +
+ data.AssetID.ToStringHyphenated());
+
+ // Add this wearable asset to the download queue
+ if (DownloadWearables)
+ {
+ PendingAssetDownload download = new PendingAssetDownload(data.AssetID, assetType);
+ DownloadQueue.Enqueue(download);
+ }
+ }
+ }
+
+ if (DownloadQueue.Count > 0)
+ {
+ PendingAssetDownload download = DownloadQueue.Dequeue();
+ Assets.RequestAsset(download.Id, download.Type, true);
+ }
+
+ // Don't download wearables twice in a row
+ DownloadWearables = false;
+ }
+
+ CallOnAgentWearables();
+ }
+
+ private void CallOnAgentWearables()
+ {
+ if (OnAgentWearables != null)
+ {
+ // Refactor our internal Wearables dictionary in to something for the callback
+ Dictionary> wearables =
+ new Dictionary>();
+
+ lock (Wearables)
+ {
+ foreach (KeyValuePair data in Wearables)
+ wearables.Add(data.Key, new KeyValuePair(data.Value.AssetID, data.Value.ItemID));
+ }
+
+ try { OnAgentWearables(wearables); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+ }
+
+ private void AgentCachedTextureResponseHandler(Packet packet, Simulator simulator)
+ {
+ Client.DebugLog("AgentCachedTextureResponseHandler()");
+
+ AgentCachedTextureResponsePacket response = (AgentCachedTextureResponsePacket)packet;
+ Dictionary paramValues = new Dictionary(VisualParams.Params.Count);
+
+ // Build a dictionary of appearance parameter indices and values from the wearables
+ foreach (KeyValuePair kvp in VisualParams.Params)
+ {
+ bool found = false;
+ VisualParam vp = kvp.Value;
+
+ // Try and find this value in our collection of downloaded wearables
+ foreach (WearableData data in Wearables.Values)
+ {
+ if (data.Wearable.Params.ContainsKey(vp.ParamID))
+ {
+ paramValues.Add(vp.ParamID,data.Wearable.Params[vp.ParamID]);
+ found = true;
+ break;
+ }
+ }
+
+ // Use a default value if we don't have one set for it
+ if (!found) paramValues.Add(vp.ParamID, vp.DefaultValue);
+ }
+
+ lock (AgentTextures)
+ {
+ foreach (AgentCachedTextureResponsePacket.WearableDataBlock block in response.WearableData)
+ {
+ // For each missing element we need to bake our own texture
+ Client.DebugLog("Cache response, index: " + block.TextureIndex + ", ID: " +
+ block.TextureID.ToStringHyphenated());
+
+ // FIXME: Use this. Right now we treat baked images on other sims as if they were missing
+ string host = Helpers.FieldToUTF8String(block.HostName);
+ if (host.Length > 0) Client.DebugLog("Cached bake exists on foreign host " + host);
+
+ BakeType bakeType = (BakeType)block.TextureIndex;
+
+ // Convert the baked index to an AgentTexture index
+ if (block.TextureID != LLUUID.Zero && host.Length == 0)
+ {
+ TextureIndex index = BakeTypeToAgentTextureIndex(bakeType);
+ AgentTextures[(int)index] = block.TextureID;
+ }
+ else
+ {
+ int imageCount = 0;
+
+ // Download all of the images in this layer
+ switch (bakeType)
+ {
+ case BakeType.Head:
+ lock (ImageDownloads)
+ {
+ imageCount += AddImageDownload(TextureIndex.HeadBodypaint);
+ //imageCount += AddImageDownload(TextureIndex.Hair);
+ }
+ break;
+ case BakeType.UpperBody:
+ lock (ImageDownloads)
+ {
+ imageCount += AddImageDownload(TextureIndex.UpperBodypaint);
+ imageCount += AddImageDownload(TextureIndex.UpperGloves);
+ imageCount += AddImageDownload(TextureIndex.UpperUndershirt);
+ imageCount += AddImageDownload(TextureIndex.UpperShirt);
+ imageCount += AddImageDownload(TextureIndex.UpperJacket);
+ }
+ break;
+ case BakeType.LowerBody:
+ lock (ImageDownloads)
+ {
+ imageCount += AddImageDownload(TextureIndex.LowerBodypaint);
+ imageCount += AddImageDownload(TextureIndex.LowerUnderpants);
+ imageCount += AddImageDownload(TextureIndex.LowerSocks);
+ imageCount += AddImageDownload(TextureIndex.LowerShoes);
+ imageCount += AddImageDownload(TextureIndex.LowerPants);
+ imageCount += AddImageDownload(TextureIndex.LowerJacket);
+ }
+ break;
+ case BakeType.Eyes:
+ lock (ImageDownloads)
+ {
+ imageCount += AddImageDownload(TextureIndex.EyesIris);
+ }
+ break;
+ case BakeType.Skirt:
+ if (Wearables.ContainsKey(Wearable.WearableType.Skirt))
+ {
+ lock (ImageDownloads)
+ {
+ imageCount += AddImageDownload(TextureIndex.Skirt);
+ }
+ }
+ break;
+ default:
+ Client.Log("Unknown BakeType " + block.TextureIndex, Helpers.LogLevel.Warning);
+ break;
+ }
+
+ if (!PendingBakes.ContainsKey(bakeType))
+ {
+ Client.DebugLog("Initializing " + bakeType.ToString() + " bake with " + imageCount + " textures");
+
+ if (imageCount == 0)
+ {
+ // if there are no textures to download, we can bake right away and start the upload
+ Baker bake = new Baker(Client, bakeType, 0, paramValues);
+ UploadBake(bake);
+ }
+ else
+ {
+ lock (PendingBakes)
+ PendingBakes.Add(bakeType, new Baker(Client, bakeType, imageCount, paramValues));
+ }
+ }
+ else if (!PendingBakes.ContainsKey(bakeType))
+ {
+ Client.Log("No cached bake for " + bakeType.ToString() + " and no textures for that " +
+ "layer, this is an unhandled case", Helpers.LogLevel.Error);
+ }
+ }
+ }
+ }
+
+ if (ImageDownloads.Count == 0)
+ {
+ // No pending downloads for baking, we're done
+ CachedResponseEvent.Set();
+ }
+ else
+ {
+ lock (ImageDownloads)
+ {
+ foreach (LLUUID image in ImageDownloads.Keys)
+ {
+ // Download all the images we need for baking
+ Assets.RequestImage(image, ImageType.Normal, 1013000.0f, 0);
+ }
+ }
+ }
+ }
+
+ private int AddImageDownload(TextureIndex index)
+ {
+ LLUUID image = AgentTextures[(int)index];
+
+ if (image != LLUUID.Zero)
+ {
+ if (!ImageDownloads.ContainsKey(image))
+ {
+ Client.DebugLog("Downloading layer " + index.ToString());
+ ImageDownloads.Add(image, index);
+ }
+
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private void Assets_OnAssetReceived(AssetDownload asset)
+ {
+ Client.DebugLog("Assets_OnAssetReceived()");
+
+ lock (Wearables)
+ {
+ // Check if this is a wearable we were waiting on
+ foreach (WearableData data in Wearables.Values)
+ {
+ if (data.AssetID == asset.AssetID)
+ {
+ // Make sure the download succeeded
+ if (asset.Success)
+ {
+ // Convert the downloaded asset to a string
+ string wearableData = Helpers.FieldToUTF8String(asset.AssetData);
+
+ // Attempt to parse the wearable data
+ if (data.Wearable.ImportAsset(wearableData))
+ {
+ Client.DebugLog("Imported wearable asset " + data.Wearable.Type.ToString());
+ Client.DebugLog(wearableData);
+
+ lock (AgentTextures)
+ {
+ foreach (KeyValuePair texture in data.Wearable.Textures)
+ {
+ Client.DebugLog("Setting " + texture.Key + " to " + texture.Value);
+ AgentTextures[(int)texture.Key] = texture.Value;
+ }
+ }
+ }
+ else
+ {
+ Client.Log("Failed to decode wearable asset " + asset.AssetID.ToStringHyphenated(),
+ Helpers.LogLevel.Warning);
+ }
+ }
+ else
+ {
+ Client.Log("Wearable " + data.Wearable.Type.ToString() + "(" +
+ asset.AssetID.ToStringHyphenated() + ") failed to download, " + asset.Status.ToString(),
+ Helpers.LogLevel.Warning);
+ }
+
+ break;
+ }
+ }
+ }
+
+ if (DownloadQueue.Count > 0)
+ {
+ // Dowload the next wearable in line
+ PendingAssetDownload download = DownloadQueue.Dequeue();
+ Assets.RequestAsset(download.Id, download.Type, true);
+ }
+ else
+ {
+ // Everything is downloaded
+ WearablesDownloadedEvent.Set();
+ }
+ }
+
+ private void Assets_OnImageReceived(ImageDownload image)
+ {
+ lock (ImageDownloads)
+ {
+ if (ImageDownloads.ContainsKey(image.ID))
+ {
+ // NOTE: this image may occupy more than one TextureIndex! We must finish this loop
+ for (int at = 0; at < AgentTextures.Length; at++)
+ {
+ if (AgentTextures[at] == image.ID)
+ {
+ TextureIndex index = (TextureIndex)at;
+ Client.DebugLog("Finished downloading texture for " + index.ToString());
+ BakeType type = Baker.BakeTypeFor(index);
+
+ //BinaryWriter writer = new BinaryWriter(File.Create("wearable_" + index.ToString() + "_" + image.ID.ToString() + ".jp2"));
+ //writer.Write(image.AssetData);
+ //writer.Close();
+
+ bool baked = false;
+
+ if (PendingBakes.ContainsKey(type))
+ {
+ if (image.Success)
+ baked = PendingBakes[type].AddTexture(index, image.AssetData);
+ else
+ {
+ Client.Log("Texture for " + index.ToString() + " failed to download, " +
+ "bake will be incomplete", Helpers.LogLevel.Warning);
+
+ baked = PendingBakes[type].MissingTexture(index);
+ }
+ }
+
+ if (baked)
+ {
+ UploadBake(PendingBakes[type]);
+ PendingBakes.Remove(type);
+ }
+
+ ImageDownloads.Remove(image.ID);
+
+ if (ImageDownloads.Count == 0 && PendingUploads.Count == 0)
+ {
+ // This is a failsafe catch, as the upload completed callback should normally
+ // be triggering the event
+ Client.DebugLog("No pending downloads or uploads detected in OnImageReceived");
+ CachedResponseEvent.Set();
+ }
+ else
+ {
+ Client.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
+ ImageDownloads.Count);
+ }
+
+ }
+ }
+ }
+ else
+ Client.Log("Received an image download callback for an image we did not request " + image.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
+ }
+ }
+
+ private void UploadBake(Baker bake)
+ {
+ // Create a transactionID and assetID for this upload
+ LLUUID transactionID = LLUUID.Random();
+ LLUUID assetID = transactionID.Combine(Client.Network.SecureSessionID);
+
+ Client.DebugLog("Bake " + bake.BakeType.ToString() + " completed. Uploading asset " + assetID.ToStringHyphenated());
+
+ // Upload the completed layer data
+ Assets.RequestUpload(transactionID, AssetType.Texture, bake.EncodedBake, true, true, false);
+
+ // Add it to a pending uploads list
+ lock (PendingUploads) PendingUploads.Add(assetID, BakeTypeToAgentTextureIndex(bake.BakeType));
+ }
+
+ private void Assets_OnAssetUploaded(AssetUpload upload)
+ {
+ lock (PendingUploads)
+ {
+ if (PendingUploads.ContainsKey(upload.AssetID))
+ {
+ if (upload.Success)
+ {
+ // Setup the TextureEntry with the new baked upload
+ TextureIndex index = PendingUploads[upload.AssetID];
+ AgentTextures[(int)index] = upload.AssetID;
+
+ Client.DebugLog("Upload complete, AgentTextures " + index.ToString() + " set to " +
+ upload.AssetID.ToStringHyphenated());
+ }
+ else
+ {
+ Client.Log("Asset upload " + upload.AssetID.ToStringHyphenated() + " failed",
+ Helpers.LogLevel.Warning);
+ }
+
+ PendingUploads.Remove(upload.AssetID);
+
+ Client.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
+ ImageDownloads.Count);
+
+ if (PendingUploads.Count == 0 && ImageDownloads.Count == 0)
+ {
+ Client.DebugLog("All pending image downloads and uploads complete");
+
+ CachedResponseEvent.Set();
+ }
+ }
+ else
+ {
+ // TEMP
+ Client.DebugLog("Upload " + upload.AssetID.ToStringHyphenated() + " was not found in PendingUploads");
+ }
+ }
+ }
+ }
+}
diff --git a/libsecondlife/AssetManager.cs b/libsecondlife/AssetManager.cs
index 5e30500b..9110fde1 100644
--- a/libsecondlife/AssetManager.cs
+++ b/libsecondlife/AssetManager.cs
@@ -1,888 +1,885 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using libsecondlife;
-using libsecondlife.Packets;
-
-namespace libsecondlife
-{
- ///
- /// The different types of assets in Second Life
- ///
- public enum AssetType : sbyte
- {
- /// Unknown asset type
- Unknown = -1,
- /// Texture asset, stores in JPEG2000 J2C stream format
- Texture = 0,
- /// Sound asset
- Sound = 1,
- /// Calling card for another avatar
- CallingCard = 2,
- /// Link to a location in world
- Landmark = 3,
- /// Legacy script asset, you should never see one of these
- [Obsolete]
- Script = 4,
- /// Collection of textures and parameters that can be
- /// worn by an avatar
- Clothing = 5,
- /// Primitive that can contain textures, sounds,
- /// scripts and more
- Object = 6,
- /// Notecard asset
- Notecard = 7,
- /// Holds a collection of inventory items
- Folder = 8,
- /// Root inventory folder
- RootFolder = 9,
- /// Linden scripting language script
- LSLText = 10,
- /// LSO bytecode for a script
- LSLBytecode = 11,
- /// Uncompressed TGA texture
- TextureTGA = 12,
- /// Collection of textures and shape parameters that can
- /// be worn
- Bodypart = 13,
- /// Trash folder
- TrashFolder = 14,
- /// Snapshot folder
- SnapshotFolder = 15,
- /// Lost and found folder
- LostAndFoundFolder = 16,
- /// Uncompressed sound
- SoundWAV = 17,
- /// Uncompressed TGA non-square image, not to be used as a
- /// texture
- ImageTGA = 18,
- /// Compressed JPEG non-square image, not to be used as a
- /// texture
- ImageJPEG = 19,
- /// Animation
- Animation = 20,
- /// Sequence of animations, sounds, chat, and pauses
- Gesture = 21,
- /// Simstate file
- Simstate = 22,
- }
-
- ///
- ///
- ///
- public enum StatusCode
- {
- /// OK
- OK = 0,
- /// Transfer completed
- Done = 1,
- ///
- Skip = 2,
- ///
- Abort = 3,
- /// Unknown error occurred
- Error = -1,
- /// Equivalent to a 404 error
- UnknownSource = -2,
- /// Client does not have permission for that resource
- InsufficientPermissiosn = -3,
- /// Unknown status
- Unknown = -4
- }
-
- ///
- ///
- ///
- public enum ChannelType : int
- {
- ///
- Unknown = 0,
- /// Unknown
- Misc = 1,
- /// Virtually all asset transfers use this channel
- Asset = 2
- }
-
- ///
- ///
- ///
- public enum SourceType : int
- {
- ///
- Unknown = 0,
- /// Arbitrary system files off the server
- [Obsolete]
- File = 1,
- /// Asset from the asset server
- Asset = 2,
- /// Inventory item
- SimInventoryItem = 3,
- ///
- SimEstate = 4
- }
-
- ///
- ///
- ///
- public enum TargetType : int
- {
- ///
- Unknown = 0,
- ///
- File,
- ///
- VFile
- }
-
- ///
- ///
- ///
- public enum ImageType : byte
- {
- ///
- Normal = 0,
- ///
- Baked = 1
- }
-
- ///
- ///
- ///
- public class Transfer
- {
- public LLUUID ID = LLUUID.Zero;
- public int Size = 0;
- public byte[] AssetData = new byte[0];
- public int Transferred = 0;
- public bool Success = false;
- }
-
- ///
- ///
- ///
- public class AssetDownload : Transfer
- {
- public LLUUID AssetID = LLUUID.Zero;
- public ChannelType Channel = ChannelType.Unknown;
- public SourceType Source = SourceType.Unknown;
- public TargetType Target = TargetType.Unknown;
- public StatusCode Status = StatusCode.Unknown;
- public float Priority = 0.0f;
- public Simulator Simulator;
-
- internal AutoResetEvent HeaderReceivedEvent = new AutoResetEvent(false);
- }
-
- ///
- ///
- ///
- public class ImageDownload : Transfer
- {
- public ushort PacketCount = 0;
- public int Codec = 0;
- public bool NotFound = false;
- public Simulator Simulator;
-
- internal int InitialDataSize = 0;
- internal AutoResetEvent HeaderReceivedEvent = new AutoResetEvent(false);
- }
-
- ///
- ///
- ///
- public class AssetUpload : Transfer
- {
- public LLUUID AssetID = LLUUID.Zero;
- public AssetType Type = AssetType.Unknown;
- public ulong XferID = 0;
- public uint PacketNum = 0;
- }
-
-
- ///
- ///
- ///
- public class AssetManager
- {
- ///
- ///
- ///
- ///
- public delegate void AssetReceivedCallback(AssetDownload asset);
- ///
- ///
- ///
- ///
- public delegate void ImageReceivedCallback(ImageDownload image);
- ///
- ///
- ///
- ///
- public delegate void AssetUploadedCallback(AssetUpload upload);
-
-
- ///
- ///
- ///
- public event AssetReceivedCallback OnAssetReceived;
- ///
- ///
- ///
- public event ImageReceivedCallback OnImageReceived;
- ///
- ///
- ///
- public event AssetUploadedCallback OnAssetUploaded;
-
- private SecondLife Client;
- private Dictionary Transfers = new Dictionary();
-
- ///
- /// Default constructor
- ///
- /// A reference to the SecondLife client object
- public AssetManager(SecondLife client)
- {
- Client = client;
-
- // Transfer packets for downloading large assets
- Client.Network.RegisterCallback(PacketType.TransferInfo, new NetworkManager.PacketCallback(TransferInfoHandler));
- Client.Network.RegisterCallback(PacketType.TransferPacket, new NetworkManager.PacketCallback(TransferPacketHandler));
-
- // Image downloading packets
- Client.Network.RegisterCallback(PacketType.ImageData, new NetworkManager.PacketCallback(ImageDataHandler));
- Client.Network.RegisterCallback(PacketType.ImagePacket, new NetworkManager.PacketCallback(ImagePacketHandler));
- Client.Network.RegisterCallback(PacketType.ImageNotInDatabase, new NetworkManager.PacketCallback(ImageNotInDatabaseHandler));
-
- // Xfer packets for uploading large assets
- Client.Network.RegisterCallback(PacketType.RequestXfer, new NetworkManager.PacketCallback(RequestXferHandler));
- Client.Network.RegisterCallback(PacketType.ConfirmXferPacket, new NetworkManager.PacketCallback(ConfirmXferPacketHandler));
- Client.Network.RegisterCallback(PacketType.AssetUploadComplete, new NetworkManager.PacketCallback(AssetUploadCompleteHandler));
- }
-
- ///
- ///
- ///
- ///
- ///
- ///
- public void RequestAsset(LLUUID assetID, AssetType type, bool priority)
- {
- AssetDownload transfer = new AssetDownload();
- transfer.ID = LLUUID.Random();
- transfer.AssetID = assetID;
- transfer.Priority = 100.0f + (priority ? 1.0f : 0.0f);
- transfer.Channel = ChannelType.Asset;
- transfer.Source = SourceType.Asset;
- transfer.Simulator = Client.Network.CurrentSim;
-
- // Add this transfer to the dictionary
- lock (Transfers) Transfers[transfer.ID] = transfer;
-
- // Build the request packet and send it
- TransferRequestPacket request = new TransferRequestPacket();
- request.TransferInfo.ChannelType = (int)transfer.Channel;
- request.TransferInfo.Priority = transfer.Priority;
- request.TransferInfo.SourceType = (int)transfer.Source;
- request.TransferInfo.TransferID = transfer.ID;
-
- byte[] paramField = new byte[20];
- Array.Copy(assetID.GetBytes(), 0, paramField, 0, 16);
- Array.Copy(Helpers.IntToBytes((int)type), 0, paramField, 16, 4);
- request.TransferInfo.Params = paramField;
-
- Client.Network.SendPacket(request, transfer.Simulator);
- }
-
- ///
- ///
- ///
- /// Use LLUUID.Zero if you do not have the
- /// asset ID but have all the necessary permissions
- /// The item ID of this asset in the inventory
- /// Use LLUUID.Zero if you are not requesting an
- /// asset from an object inventory
- /// The owner of this asset
- /// Asset type
- /// Whether to prioritize this asset download or not
- public void RequestInventoryAsset(LLUUID assetID, LLUUID itemID, LLUUID taskID, LLUUID ownerID, AssetType type,
- bool priority)
- {
- AssetDownload transfer = new AssetDownload();
- transfer.ID = LLUUID.Random();
- transfer.AssetID = assetID;
- transfer.Priority = 100.0f + (priority ? 1.0f : 0.0f);
- transfer.Channel = ChannelType.Asset;
- transfer.Source = SourceType.SimInventoryItem;
- transfer.Simulator = Client.Network.CurrentSim;
-
- // Add this transfer to the dictionary
- lock (Transfers) Transfers[transfer.ID] = transfer;
-
- // Build the request packet and send it
- TransferRequestPacket request = new TransferRequestPacket();
- request.TransferInfo.ChannelType = (int)transfer.Channel;
- request.TransferInfo.Priority = transfer.Priority;
- request.TransferInfo.SourceType = (int)transfer.Source;
- request.TransferInfo.TransferID = transfer.ID;
-
- byte[] paramField = new byte[100];
- Array.Copy(Client.Network.AgentID.GetBytes(), 0, paramField, 0, 16);
- Array.Copy(Client.Network.SessionID.GetBytes(), 0, paramField, 16, 16);
- Array.Copy(ownerID.GetBytes(), 0, paramField, 32, 16);
- Array.Copy(taskID.GetBytes(), 0, paramField, 48, 16);
- Array.Copy(itemID.GetBytes(), 0, paramField, 64, 16);
- Array.Copy(assetID.GetBytes(), 0, paramField, 80, 16);
- Array.Copy(Helpers.IntToBytes((int)type), 0, paramField, 96, 4);
- request.TransferInfo.Params = paramField;
-
- Client.Network.SendPacket(request, transfer.Simulator);
- }
-
- public void RequestEstateAsset()
- {
- throw new Exception("This function is not implemented yet!");
- }
-
- ///
- /// Initiate an image download. This is an asynchronous function
- ///
- /// The image to download
- ///
- ///
- ///
- public void RequestImage(LLUUID imageID, ImageType type, float priority, int discardLevel)
- {
- if (!Transfers.ContainsKey(imageID))
- {
- ImageDownload transfer = new ImageDownload();
- transfer.ID = imageID;
- transfer.Simulator = Client.Network.CurrentSim;
-
- // Add this transfer to the dictionary
- lock (Transfers) Transfers[transfer.ID] = transfer;
-
- // Build and send the request packet
- RequestImagePacket request = new RequestImagePacket();
- request.AgentData.AgentID = Client.Network.AgentID;
- request.AgentData.SessionID = Client.Network.SessionID;
- request.RequestImage = new RequestImagePacket.RequestImageBlock[1];
- request.RequestImage[0] = new RequestImagePacket.RequestImageBlock();
- request.RequestImage[0].DiscardLevel = (sbyte)discardLevel;
- request.RequestImage[0].DownloadPriority = priority;
- request.RequestImage[0].Packet = 0;
- request.RequestImage[0].Image = imageID;
- request.RequestImage[0].Type = (byte)type;
-
- Client.Network.SendPacket(request, transfer.Simulator);
- }
- else
- {
- Client.Log("RequestImage() called for an image we are already downloading, ignoring",
- Helpers.LogLevel.Info);
- }
- }
-
- ///
- ///
- ///
- /// Usually a randomly generated UUID
- ///
- ///
- ///
- ///
- ///
- public void RequestUpload(LLUUID transactionID, AssetType type, byte[] data, bool tempFile, bool storeLocal,
- bool isPriority)
- {
- if (!Transfers.ContainsKey(transactionID))
- {
- AssetUpload upload = new AssetUpload();
- upload.AssetData = data;
- upload.ID = transactionID;
- upload.AssetID = ((transactionID == LLUUID.Zero) ? transactionID : transactionID.Combine(Client.Network.SecureSessionID));
- upload.Size = data.Length;
- upload.XferID = 0;
-
- // Build and send the upload packet
- AssetUploadRequestPacket request = new AssetUploadRequestPacket();
- request.AssetBlock.StoreLocal = storeLocal;
- request.AssetBlock.Tempfile = tempFile;
- request.AssetBlock.TransactionID = upload.ID;
- request.AssetBlock.Type = (sbyte)type;
-
- if (data.Length + 100 < Settings.MAX_PACKET_SIZE)
- {
- Client.Log(
- String.Format("Beginning asset upload [Single Packet], ID: {0}, AssetID: {1}, Size: {2}",
- upload.ID.ToStringHyphenated(), upload.AssetID.ToStringHyphenated(), upload.Size),
- Helpers.LogLevel.Info);
-
- // The whole asset will fit in this packet, makes things easy
- request.AssetBlock.AssetData = data;
- upload.Transferred = data.Length;
- }
- else
- {
- Client.Log(
- String.Format("Beginning asset upload [Multiple Packets], ID: {0}, AssetID: {1}, Size: {2}",
- upload.ID.ToStringHyphenated(), upload.AssetID.ToStringHyphenated(), upload.Size),
- Helpers.LogLevel.Info);
-
- // Asset is too big, send in multiple packets
- request.AssetBlock.AssetData = new byte[0];
- }
-
- //Client.DebugLog(request.ToString());
-
- // Add this upload to the Transfers dictionary using the assetID as the key.
- // Once the simulator assigns an actual identifier for this upload it will be
- // removed from Transfers and reinserted with the proper identifier
- lock (Transfers) Transfers[upload.AssetID] = upload;
-
- Client.Network.SendPacket(request);
- }
- else
- {
- Client.Log("RequestUpload() called for an asset we are already uploading, ignoring",
- Helpers.LogLevel.Info);
- }
- }
-
- private void SendNextUploadPacket(AssetUpload upload)
- {
- SendXferPacketPacket send = new SendXferPacketPacket();
-
- send.XferID.ID = upload.XferID;
- send.XferID.Packet = upload.PacketNum++;
-
- if (send.XferID.Packet == 0)
- {
- // The first packet reserves the first four bytes of the data for the
- // total length of the asset and appends 1000 bytes of data after that
- send.DataPacket.Data = new byte[1004];
- Buffer.BlockCopy(Helpers.IntToBytes(upload.Size), 0, send.DataPacket.Data, 0, 4);
- Buffer.BlockCopy(upload.AssetData, 0, send.DataPacket.Data, 4, 1000);
- upload.Transferred += 1000;
- }
- else if ((send.XferID.Packet + 1) * 1000 < upload.Size)
- {
- // This packet is somewhere in the middle of the transfer, or a perfectly
- // aligned packet at the end of the transfer
- send.DataPacket.Data = new byte[1000];
- Buffer.BlockCopy(upload.AssetData, upload.Transferred, send.DataPacket.Data, 0, 1000);
- upload.Transferred += 1000;
- }
- else
- {
- // Special handler for the last packet which will be less than 1000 bytes
- int lastlen = upload.Size - ((int)send.XferID.Packet * 1000);
- send.DataPacket.Data = new byte[lastlen];
- Buffer.BlockCopy(upload.AssetData, (int)send.XferID.Packet * 1000, send.DataPacket.Data, 0, lastlen);
- send.XferID.Packet |= (uint)0x80000000; // This signals the final packet
- upload.Transferred += lastlen;
- }
-
- Client.Network.SendPacket(send);
- }
-
- private void TransferInfoHandler(Packet packet, Simulator simulator)
- {
- if (OnAssetReceived != null)
- {
- TransferInfoPacket info = (TransferInfoPacket)packet;
-
- if (Transfers.ContainsKey(info.TransferInfo.TransferID))
- {
- AssetDownload transfer = (AssetDownload)Transfers[info.TransferInfo.TransferID];
-
- transfer.Channel = (ChannelType)info.TransferInfo.ChannelType;
- transfer.Status = (StatusCode)info.TransferInfo.Status;
- transfer.Target = (TargetType)info.TransferInfo.TargetType;
- transfer.Size = info.TransferInfo.Size;
-
- // TODO: Once we support mid-transfer status checking and aborting this
- // will need to become smarter
- if (transfer.Status != StatusCode.OK)
- {
- Client.Log("Transfer failed with status code " + transfer.Status, Helpers.LogLevel.Warning);
-
- lock (Transfers) Transfers.Remove(transfer.ID);
-
- // No data could have been received before the TransferInfo packet
- transfer.AssetData = null;
-
- // Fire the event with our transfer that contains Success = false;
- try { OnAssetReceived(transfer); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
- }
- else
- {
- transfer.AssetData = new byte[transfer.Size];
-
- if (transfer.Source == SourceType.Asset && info.TransferInfo.Params.Length == 20)
- {
- transfer.AssetID = new LLUUID(info.TransferInfo.Params, 0);
- AssetType type = (AssetType)(int)Helpers.BytesToUInt(info.TransferInfo.Params, 16);
-
- //Client.DebugLog(String.Format("TransferInfo packet received. AssetID: {0} Type: {1}",
- // transfer.AssetID, type));
- }
- else if (transfer.Source == SourceType.SimInventoryItem && info.TransferInfo.Params.Length == 100)
- {
- // TODO: Can we use these?
- LLUUID agentID = new LLUUID(info.TransferInfo.Params, 0);
- LLUUID sessionID = new LLUUID(info.TransferInfo.Params, 16);
- LLUUID ownerID = new LLUUID(info.TransferInfo.Params, 32);
- LLUUID taskID = new LLUUID(info.TransferInfo.Params, 48);
- LLUUID itemID = new LLUUID(info.TransferInfo.Params, 64);
- transfer.AssetID = new LLUUID(info.TransferInfo.Params, 80);
- AssetType type = (AssetType)(int)Helpers.BytesToUInt(info.TransferInfo.Params, 96);
-
- //Client.DebugLog(String.Format("TransferInfo packet received. AgentID: {0} SessionID: {1} " +
- // "OwnerID: {2} TaskID: {3} ItemID: {4} AssetID: {5} Type: {6}", agentID, sessionID,
- // ownerID, taskID, itemID, transfer.AssetID, type));
- }
- else
- {
- Client.Log("Received a TransferInfo packet with a SourceType of " + transfer.Source.ToString() +
- " and a Params field length of " + info.TransferInfo.Params.Length,
- Helpers.LogLevel.Warning);
- }
- }
- }
- else
- {
- Client.Log("Received a TransferInfo packet for an asset we didn't request, TransferID: " +
- info.TransferInfo.TransferID, Helpers.LogLevel.Warning);
- }
- }
- }
-
- private void TransferPacketHandler(Packet packet, Simulator simulator)
- {
- TransferPacketPacket asset = (TransferPacketPacket)packet;
-
- if (Transfers.ContainsKey(asset.TransferData.TransferID))
- {
- AssetDownload transfer = (AssetDownload)Transfers[asset.TransferData.TransferID];
-
- if (transfer.Size == 0)
- {
- Client.DebugLog("TransferPacket received ahead of the transfer header, blocking...");
-
- // We haven't received the header yet, block until it's received or times out
- transfer.HeaderReceivedEvent.WaitOne(1000 * 20, false);
-
- if (transfer.Size == 0)
- {
- Client.Log("Timed out while waiting for the asset header to download for " +
- transfer.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
-
- // Abort the transfer
- TransferAbortPacket abort = new TransferAbortPacket();
- abort.TransferInfo.ChannelType = (int)transfer.Channel;
- abort.TransferInfo.TransferID = transfer.ID;
- Client.Network.SendPacket(abort, transfer.Simulator);
-
- transfer.Success = false;
- lock (Transfers) Transfers.Remove(transfer.ID);
-
- // Fire the event with our transfer that contains Success = false
- if (OnAssetReceived != null)
- {
- try { OnAssetReceived(transfer); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
- }
-
- return;
- }
- }
-
- // This assumes that every transfer packet except the last one is exactly 1000 bytes,
- // hopefully that is a safe assumption to make
- Buffer.BlockCopy(asset.TransferData.Data, 0, transfer.AssetData, 1000 * asset.TransferData.Packet,
- asset.TransferData.Data.Length);
- transfer.Transferred += asset.TransferData.Data.Length;
-
- //Client.DebugLog(String.Format("Transfer packet {0}, received {1}/{2}/{3} bytes for asset {4}",
- // asset.TransferData.Packet, asset.TransferData.Data.Length, transfer.Transferred, transfer.Size,
- // transfer.AssetID.ToStringHyphenated()));
-
- // Check if we downloaded the full asset
- if (transfer.Transferred >= transfer.Size)
- {
- Client.DebugLog("Transfer for asset " + transfer.AssetID.ToStringHyphenated() + " completed");
-
- transfer.Success = true;
- lock (Transfers) Transfers.Remove(transfer.ID);
-
- if (OnAssetReceived != null)
- {
- try { OnAssetReceived(transfer); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
- }
- }
- }
- else
- {
- //Client.DebugLog("Received a TransferPacket for unknown transfer " +
- // asset.TransferData.TransferID.ToStringHyphenated());
- }
- }
-
- private void RequestXferHandler(Packet packet, Simulator simulator)
- {
- AssetUpload upload = null;
- RequestXferPacket request = (RequestXferPacket)packet;
-
- // The Xfer system sucks. This will thankfully die soon when uploads are
- // moved to HTTP
- lock (Transfers)
- {
- // Associate the XferID with an upload. If an upload is initiated
- // before the previous one is associated with an XferID one or both
- // of them will undoubtedly fail
- foreach (Transfer transfer in Transfers.Values)
- {
- if (transfer is AssetUpload)
- {
- if (((AssetUpload)transfer).XferID == 0)
- {
- // First match, use it
- upload = (AssetUpload)transfer;
- upload.XferID = request.XferID.ID;
- upload.Type = (AssetType)request.XferID.VFileType;
-
- // Remove this upload from the Transfers dictionary and re-insert
- // it using the transferID as the key instead of the assetID
- Transfers.Remove(upload.AssetID);
-
- LLUUID transferID = new LLUUID(upload.XferID);
- Transfers[transferID] = upload;
-
- // Send the first packet containing actual asset data
- SendNextUploadPacket(upload);
-
- return;
- }
- }
- }
- }
- }
-
- private void ConfirmXferPacketHandler(Packet packet, Simulator simulator)
- {
- ConfirmXferPacketPacket confirm = (ConfirmXferPacketPacket)packet;
-
- // Building a new UUID every time an ACK is received for an upload is a horrible
- // thing, but this whole Xfer system is horrible
- LLUUID transferID = new LLUUID(confirm.XferID.ID);
- AssetUpload upload = null;
-
- lock (Transfers)
- {
- if (Transfers.ContainsKey(transferID))
- {
- upload = (AssetUpload)Transfers[transferID];
-
- //Client.DebugLog(String.Format("ACK for upload {0} of asset type {1} ({2}/{3})",
- // upload.AssetID.ToStringHyphenated(), upload.Type, upload.Transferred, upload.Size));
-
- if (upload.Transferred < upload.Size)
- SendNextUploadPacket((AssetUpload)Transfers[transferID]);
- }
- }
- }
-
- private void AssetUploadCompleteHandler(Packet packet, Simulator simulator)
- {
- AssetUploadCompletePacket complete = (AssetUploadCompletePacket)packet;
-
- Client.DebugLog(complete.ToString());
-
- bool found = false;
- KeyValuePair foundTransfer = new KeyValuePair();
-
- // Xfer system sucks really really bad. Where is the damn XferID?
- lock (Transfers)
- {
- foreach (KeyValuePair transfer in Transfers)
- {
- if (transfer.Value.GetType() == typeof(AssetUpload))
- {
- AssetUpload upload = (AssetUpload)transfer.Value;
-
- if (upload.AssetID == complete.AssetBlock.UUID)
- {
- found = true;
- foundTransfer = transfer;
- upload.Success = complete.AssetBlock.Success;
- upload.Type = (AssetType)complete.AssetBlock.Type;
- break;
- }
- }
- }
- }
-
- if (found)
- {
- lock (Transfers) Transfers.Remove(foundTransfer.Key);
-
- if (OnAssetUploaded != null)
- {
- try { OnAssetUploaded((AssetUpload)foundTransfer.Value); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
- }
- }
- }
-
- ///
- /// Handles the Image Data packet which includes the ID and Size of the image,
- /// along with the first block of data for the image. If the image is small enough
- /// there will be no additional packets
- ///
- public void ImageDataHandler(Packet packet, Simulator simulator)
- {
- ImageDataPacket data = (ImageDataPacket)packet;
- ImageDownload transfer = null;
-
- lock (Transfers)
- {
- if (Transfers.ContainsKey(data.ImageID.ID))
- {
- transfer = (ImageDownload)Transfers[data.ImageID.ID];
-
- //Client.DebugLog("Received first " + data.ImageData.Data.Length + " bytes for image " +
- // data.ImageID.ID.ToStringHyphenated());
-
- transfer.Codec = data.ImageID.Codec;
- transfer.PacketCount = data.ImageID.Packets;
- transfer.Size = (int)data.ImageID.Size;
- transfer.AssetData = new byte[transfer.Size];
- Buffer.BlockCopy(data.ImageData.Data, 0, transfer.AssetData, 0, data.ImageData.Data.Length);
- transfer.InitialDataSize = data.ImageData.Data.Length;
- transfer.Transferred += data.ImageData.Data.Length;
-
- // Check if we downloaded the full image
- if (transfer.Transferred >= transfer.Size)
- {
- Transfers.Remove(transfer.ID);
- transfer.Success = true;
- }
- }
- }
-
- if (transfer != null)
- {
- transfer.HeaderReceivedEvent.Set();
-
- if (OnImageReceived != null && transfer.Transferred >= transfer.Size)
- {
- try { OnImageReceived(transfer); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
- }
- }
- }
-
- public void LogTransfer(LLUUID id)
- {
- lock (Transfers)
- {
- if (Transfers.ContainsKey(id))
- Client.DebugLog("Transfer " + id + ": " + Transfers[id].Transferred + "/" + Transfers[id].Size + "\n");
- }
- }
-
- public void LogAllTransfers()
- {
- lock (Transfers)
- {
- foreach (KeyValuePair transfer in Transfers)
- Client.DebugLog("Transfer " + transfer.Value.ID + ": " + transfer.Value.Transferred + "/" + transfer.Value.Size + "\n");
- }
- }
-
- ///
- /// Handles the remaining Image data that did not fit in the initial ImageData packet
- ///
- public void ImagePacketHandler(Packet packet, Simulator simulator)
- {
- ImagePacketPacket image = (ImagePacketPacket)packet;
- ImageDownload transfer = null;
-
- lock (Transfers)
- {
- if (Transfers.ContainsKey(image.ImageID.ID))
- {
- transfer = (ImageDownload)Transfers[image.ImageID.ID];
-
- if (transfer.Size == 0)
- {
- // We haven't received the header yet, block until it's received or times out
- transfer.HeaderReceivedEvent.WaitOne(1000 * 20, false);
-
- if (transfer.Size == 0)
- {
- Client.Log("Timed out while waiting for the image header to download for " +
- transfer.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
-
- transfer.Success = false;
- Transfers.Remove(transfer.ID);
- goto Callback;
- }
- }
-
- // The header is downloaded, we can insert this data in to the proper position
- Array.Copy(image.ImageData.Data, 0, transfer.AssetData, transfer.InitialDataSize +
- (1000 * (image.ImageID.Packet - 1)), image.ImageData.Data.Length);
- transfer.Transferred += image.ImageData.Data.Length;
-
- //Client.DebugLog("Received " + image.ImageData.Data.Length + "/" + transfer.Transferred +
- // "/" + transfer.Size + " bytes for image " + image.ImageID.ID.ToStringHyphenated());
-
- // Check if we downloaded the full image
- if (transfer.Transferred >= transfer.Size)
- {
- transfer.Success = true;
- Transfers.Remove(transfer.ID);
- }
- }
- }
-
- Callback:
-
- if (transfer != null && OnImageReceived != null && (transfer.Transferred >= transfer.Size || transfer.Size == 0))
- {
- try { OnImageReceived(transfer); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
- }
- }
-
- ///
- /// The requested image does not exist on the asset server
- ///
- public void ImageNotInDatabaseHandler(Packet packet, Simulator simulator)
- {
- ImageNotInDatabasePacket notin = (ImageNotInDatabasePacket)packet;
- ImageDownload transfer = null;
-
- lock (Transfers)
- {
- if (Transfers.ContainsKey(notin.ImageID.ID))
- {
- transfer = (ImageDownload)Transfers[notin.ImageID.ID];
- transfer.NotFound = true;
- Transfers.Remove(transfer.ID);
- }
- }
-
- // Fire the event with our transfer that contains Success = false;
- if (transfer != null && OnImageReceived != null)
- {
- try { OnImageReceived(transfer); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
- }
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using libsecondlife;
+using libsecondlife.Packets;
+
+namespace libsecondlife
+{
+ ///
+ /// The different types of assets in Second Life
+ ///
+ public enum AssetType : sbyte
+ {
+ /// Unknown asset type
+ Unknown = -1,
+ /// Texture asset, stores in JPEG2000 J2C stream format
+ Texture = 0,
+ /// Sound asset
+ Sound = 1,
+ /// Calling card for another avatar
+ CallingCard = 2,
+ /// Link to a location in world
+ Landmark = 3,
+ /// Legacy script asset, you should never see one of these
+ [Obsolete]
+ Script = 4,
+ /// Collection of textures and parameters that can be
+ /// worn by an avatar
+ Clothing = 5,
+ /// Primitive that can contain textures, sounds,
+ /// scripts and more
+ Object = 6,
+ /// Notecard asset
+ Notecard = 7,
+ /// Holds a collection of inventory items
+ Folder = 8,
+ /// Root inventory folder
+ RootFolder = 9,
+ /// Linden scripting language script
+ LSLText = 10,
+ /// LSO bytecode for a script
+ LSLBytecode = 11,
+ /// Uncompressed TGA texture
+ TextureTGA = 12,
+ /// Collection of textures and shape parameters that can
+ /// be worn
+ Bodypart = 13,
+ /// Trash folder
+ TrashFolder = 14,
+ /// Snapshot folder
+ SnapshotFolder = 15,
+ /// Lost and found folder
+ LostAndFoundFolder = 16,
+ /// Uncompressed sound
+ SoundWAV = 17,
+ /// Uncompressed TGA non-square image, not to be used as a
+ /// texture
+ ImageTGA = 18,
+ /// Compressed JPEG non-square image, not to be used as a
+ /// texture
+ ImageJPEG = 19,
+ /// Animation
+ Animation = 20,
+ /// Sequence of animations, sounds, chat, and pauses
+ Gesture = 21,
+ /// Simstate file
+ Simstate = 22,
+ }
+
+ ///
+ ///
+ ///
+ public enum StatusCode
+ {
+ /// OK
+ OK = 0,
+ /// Transfer completed
+ Done = 1,
+ ///
+ Skip = 2,
+ ///
+ Abort = 3,
+ /// Unknown error occurred
+ Error = -1,
+ /// Equivalent to a 404 error
+ UnknownSource = -2,
+ /// Client does not have permission for that resource
+ InsufficientPermissiosn = -3,
+ /// Unknown status
+ Unknown = -4
+ }
+
+ ///
+ ///
+ ///
+ public enum ChannelType : int
+ {
+ ///
+ Unknown = 0,
+ /// Unknown
+ Misc = 1,
+ /// Virtually all asset transfers use this channel
+ Asset = 2
+ }
+
+ ///
+ ///
+ ///
+ public enum SourceType : int
+ {
+ ///
+ Unknown = 0,
+ /// Arbitrary system files off the server
+ [Obsolete]
+ File = 1,
+ /// Asset from the asset server
+ Asset = 2,
+ /// Inventory item
+ SimInventoryItem = 3,
+ ///
+ SimEstate = 4
+ }
+
+ ///
+ ///
+ ///
+ public enum TargetType : int
+ {
+ ///
+ Unknown = 0,
+ ///
+ File,
+ ///
+ VFile
+ }
+
+ ///
+ ///
+ ///
+ public enum ImageType : byte
+ {
+ ///
+ Normal = 0,
+ ///
+ Baked = 1
+ }
+
+ ///
+ ///
+ ///
+ public class Transfer
+ {
+ public LLUUID ID = LLUUID.Zero;
+ public int Size = 0;
+ public byte[] AssetData = new byte[0];
+ public int Transferred = 0;
+ public bool Success = false;
+ }
+
+ ///
+ ///
+ ///
+ public class AssetDownload : Transfer
+ {
+ public LLUUID AssetID = LLUUID.Zero;
+ public ChannelType Channel = ChannelType.Unknown;
+ public SourceType Source = SourceType.Unknown;
+ public TargetType Target = TargetType.Unknown;
+ public StatusCode Status = StatusCode.Unknown;
+ public float Priority = 0.0f;
+ public Simulator Simulator;
+
+ internal AutoResetEvent HeaderReceivedEvent = new AutoResetEvent(false);
+ }
+
+ ///
+ ///
+ ///
+ public class ImageDownload : Transfer
+ {
+ public ushort PacketCount = 0;
+ public int Codec = 0;
+ public bool NotFound = false;
+ public Simulator Simulator;
+
+ internal int InitialDataSize = 0;
+ internal AutoResetEvent HeaderReceivedEvent = new AutoResetEvent(false);
+ }
+
+ ///
+ ///
+ ///
+ public class AssetUpload : Transfer
+ {
+ public LLUUID AssetID = LLUUID.Zero;
+ public AssetType Type = AssetType.Unknown;
+ public ulong XferID = 0;
+ public uint PacketNum = 0;
+ }
+
+
+ ///
+ ///
+ ///
+ public class AssetManager
+ {
+ ///
+ ///
+ ///
+ ///
+ public delegate void AssetReceivedCallback(AssetDownload asset);
+ ///
+ ///
+ ///
+ ///
+ public delegate void ImageReceivedCallback(ImageDownload image);
+ ///
+ ///
+ ///
+ ///
+ public delegate void AssetUploadedCallback(AssetUpload upload);
+ ///
+ ///
+ ///
+ ///
+ public delegate void UploadProgressCallback(AssetUpload upload);
+
+ ///
+ ///
+ ///
+ public event AssetReceivedCallback OnAssetReceived;
+ ///
+ ///
+ ///
+ public event ImageReceivedCallback OnImageReceived;
+ ///
+ ///
+ ///
+ public event AssetUploadedCallback OnAssetUploaded;
+ ///
+ ///
+ ///
+ public event UploadProgressCallback OnUploadProgress;
+
+ private SecondLife Client;
+ private Dictionary Transfers = new Dictionary();
+
+ ///
+ /// Default constructor
+ ///
+ /// A reference to the SecondLife client object
+ public AssetManager(SecondLife client)
+ {
+ Client = client;
+
+ // Transfer packets for downloading large assets
+ Client.Network.RegisterCallback(PacketType.TransferInfo, new NetworkManager.PacketCallback(TransferInfoHandler));
+ Client.Network.RegisterCallback(PacketType.TransferPacket, new NetworkManager.PacketCallback(TransferPacketHandler));
+
+ // Image downloading packets
+ Client.Network.RegisterCallback(PacketType.ImageData, new NetworkManager.PacketCallback(ImageDataHandler));
+ Client.Network.RegisterCallback(PacketType.ImagePacket, new NetworkManager.PacketCallback(ImagePacketHandler));
+ Client.Network.RegisterCallback(PacketType.ImageNotInDatabase, new NetworkManager.PacketCallback(ImageNotInDatabaseHandler));
+
+ // Xfer packets for uploading large assets
+ Client.Network.RegisterCallback(PacketType.RequestXfer, new NetworkManager.PacketCallback(RequestXferHandler));
+ Client.Network.RegisterCallback(PacketType.ConfirmXferPacket, new NetworkManager.PacketCallback(ConfirmXferPacketHandler));
+ Client.Network.RegisterCallback(PacketType.AssetUploadComplete, new NetworkManager.PacketCallback(AssetUploadCompleteHandler));
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void RequestAsset(LLUUID assetID, AssetType type, bool priority)
+ {
+ AssetDownload transfer = new AssetDownload();
+ transfer.ID = LLUUID.Random();
+ transfer.AssetID = assetID;
+ transfer.Priority = 100.0f + (priority ? 1.0f : 0.0f);
+ transfer.Channel = ChannelType.Asset;
+ transfer.Source = SourceType.Asset;
+ transfer.Simulator = Client.Network.CurrentSim;
+
+ // Add this transfer to the dictionary
+ lock (Transfers) Transfers[transfer.ID] = transfer;
+
+ // Build the request packet and send it
+ TransferRequestPacket request = new TransferRequestPacket();
+ request.TransferInfo.ChannelType = (int)transfer.Channel;
+ request.TransferInfo.Priority = transfer.Priority;
+ request.TransferInfo.SourceType = (int)transfer.Source;
+ request.TransferInfo.TransferID = transfer.ID;
+
+ byte[] paramField = new byte[20];
+ Array.Copy(assetID.GetBytes(), 0, paramField, 0, 16);
+ Array.Copy(Helpers.IntToBytes((int)type), 0, paramField, 16, 4);
+ request.TransferInfo.Params = paramField;
+
+ Client.Network.SendPacket(request, transfer.Simulator);
+ }
+
+ ///
+ ///
+ ///
+ /// Use LLUUID.Zero if you do not have the
+ /// asset ID but have all the necessary permissions
+ /// The item ID of this asset in the inventory
+ /// Use LLUUID.Zero if you are not requesting an
+ /// asset from an object inventory
+ /// The owner of this asset
+ /// Asset type
+ /// Whether to prioritize this asset download or not
+ public void RequestInventoryAsset(LLUUID assetID, LLUUID itemID, LLUUID taskID, LLUUID ownerID, AssetType type,
+ bool priority)
+ {
+ AssetDownload transfer = new AssetDownload();
+ transfer.ID = LLUUID.Random();
+ transfer.AssetID = assetID;
+ transfer.Priority = 100.0f + (priority ? 1.0f : 0.0f);
+ transfer.Channel = ChannelType.Asset;
+ transfer.Source = SourceType.SimInventoryItem;
+ transfer.Simulator = Client.Network.CurrentSim;
+
+ // Add this transfer to the dictionary
+ lock (Transfers) Transfers[transfer.ID] = transfer;
+
+ // Build the request packet and send it
+ TransferRequestPacket request = new TransferRequestPacket();
+ request.TransferInfo.ChannelType = (int)transfer.Channel;
+ request.TransferInfo.Priority = transfer.Priority;
+ request.TransferInfo.SourceType = (int)transfer.Source;
+ request.TransferInfo.TransferID = transfer.ID;
+
+ byte[] paramField = new byte[100];
+ Array.Copy(Client.Network.AgentID.GetBytes(), 0, paramField, 0, 16);
+ Array.Copy(Client.Network.SessionID.GetBytes(), 0, paramField, 16, 16);
+ Array.Copy(ownerID.GetBytes(), 0, paramField, 32, 16);
+ Array.Copy(taskID.GetBytes(), 0, paramField, 48, 16);
+ Array.Copy(itemID.GetBytes(), 0, paramField, 64, 16);
+ Array.Copy(assetID.GetBytes(), 0, paramField, 80, 16);
+ Array.Copy(Helpers.IntToBytes((int)type), 0, paramField, 96, 4);
+ request.TransferInfo.Params = paramField;
+
+ Client.Network.SendPacket(request, transfer.Simulator);
+ }
+
+ public void RequestEstateAsset()
+ {
+ throw new Exception("This function is not implemented yet!");
+ }
+
+ ///
+ /// Initiate an image download. This is an asynchronous function
+ ///
+ /// The image to download
+ ///
+ ///
+ ///
+ public void RequestImage(LLUUID imageID, ImageType type, float priority, int discardLevel)
+ {
+ if (!Transfers.ContainsKey(imageID))
+ {
+ ImageDownload transfer = new ImageDownload();
+ transfer.ID = imageID;
+ transfer.Simulator = Client.Network.CurrentSim;
+
+ // Add this transfer to the dictionary
+ lock (Transfers) Transfers[transfer.ID] = transfer;
+
+ // Build and send the request packet
+ RequestImagePacket request = new RequestImagePacket();
+ request.AgentData.AgentID = Client.Network.AgentID;
+ request.AgentData.SessionID = Client.Network.SessionID;
+ request.RequestImage = new RequestImagePacket.RequestImageBlock[1];
+ request.RequestImage[0] = new RequestImagePacket.RequestImageBlock();
+ request.RequestImage[0].DiscardLevel = (sbyte)discardLevel;
+ request.RequestImage[0].DownloadPriority = priority;
+ request.RequestImage[0].Packet = 0;
+ request.RequestImage[0].Image = imageID;
+ request.RequestImage[0].Type = (byte)type;
+
+ Client.Network.SendPacket(request, transfer.Simulator);
+ }
+ else
+ {
+ Client.Log("RequestImage() called for an image we are already downloading, ignoring",
+ Helpers.LogLevel.Info);
+ }
+ }
+
+ ///
+ ///
+ ///
+ /// Usually a randomly generated UUID
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void RequestUpload(LLUUID transactionID, AssetType type, byte[] data, bool tempFile, bool storeLocal,
+ bool isPriority)
+ {
+ if (!Transfers.ContainsKey(transactionID))
+ {
+ AssetUpload upload = new AssetUpload();
+ upload.AssetData = data;
+ upload.ID = transactionID;
+ upload.AssetID = ((transactionID == LLUUID.Zero) ? transactionID : transactionID.Combine(Client.Network.SecureSessionID));
+ upload.Size = data.Length;
+ upload.XferID = 0;
+
+ // Build and send the upload packet
+ AssetUploadRequestPacket request = new AssetUploadRequestPacket();
+ request.AssetBlock.StoreLocal = storeLocal;
+ request.AssetBlock.Tempfile = tempFile;
+ request.AssetBlock.TransactionID = upload.ID;
+ request.AssetBlock.Type = (sbyte)type;
+
+ if (data.Length + 100 < Settings.MAX_PACKET_SIZE)
+ {
+ Client.Log(
+ String.Format("Beginning asset upload [Single Packet], ID: {0}, AssetID: {1}, Size: {2}",
+ upload.ID.ToStringHyphenated(), upload.AssetID.ToStringHyphenated(), upload.Size),
+ Helpers.LogLevel.Info);
+
+ // The whole asset will fit in this packet, makes things easy
+ request.AssetBlock.AssetData = data;
+ upload.Transferred = data.Length;
+ }
+ else
+ {
+ Client.Log(
+ String.Format("Beginning asset upload [Multiple Packets], ID: {0}, AssetID: {1}, Size: {2}",
+ upload.ID.ToStringHyphenated(), upload.AssetID.ToStringHyphenated(), upload.Size),
+ Helpers.LogLevel.Info);
+
+ // Asset is too big, send in multiple packets
+ request.AssetBlock.AssetData = new byte[0];
+ }
+
+ //Client.DebugLog(request.ToString());
+
+ // Add this upload to the Transfers dictionary using the assetID as the key.
+ // Once the simulator assigns an actual identifier for this upload it will be
+ // removed from Transfers and reinserted with the proper identifier
+ lock (Transfers) Transfers[upload.AssetID] = upload;
+
+ Client.Network.SendPacket(request);
+ }
+ else
+ {
+ Client.Log("RequestUpload() called for an asset we are already uploading, ignoring",
+ Helpers.LogLevel.Info);
+ }
+ }
+
+ private void SendNextUploadPacket(AssetUpload upload)
+ {
+ SendXferPacketPacket send = new SendXferPacketPacket();
+
+ send.XferID.ID = upload.XferID;
+ send.XferID.Packet = upload.PacketNum++;
+
+ if (send.XferID.Packet == 0)
+ {
+ // The first packet reserves the first four bytes of the data for the
+ // total length of the asset and appends 1000 bytes of data after that
+ send.DataPacket.Data = new byte[1004];
+ Buffer.BlockCopy(Helpers.IntToBytes(upload.Size), 0, send.DataPacket.Data, 0, 4);
+ Buffer.BlockCopy(upload.AssetData, 0, send.DataPacket.Data, 4, 1000);
+ upload.Transferred += 1000;
+ }
+ else if ((send.XferID.Packet + 1) * 1000 < upload.Size)
+ {
+ // This packet is somewhere in the middle of the transfer, or a perfectly
+ // aligned packet at the end of the transfer
+ send.DataPacket.Data = new byte[1000];
+ Buffer.BlockCopy(upload.AssetData, upload.Transferred, send.DataPacket.Data, 0, 1000);
+ upload.Transferred += 1000;
+ }
+ else
+ {
+ // Special handler for the last packet which will be less than 1000 bytes
+ int lastlen = upload.Size - ((int)send.XferID.Packet * 1000);
+ send.DataPacket.Data = new byte[lastlen];
+ Buffer.BlockCopy(upload.AssetData, (int)send.XferID.Packet * 1000, send.DataPacket.Data, 0, lastlen);
+ send.XferID.Packet |= (uint)0x80000000; // This signals the final packet
+ upload.Transferred += lastlen;
+ }
+
+ Client.Network.SendPacket(send);
+ }
+
+ private void TransferInfoHandler(Packet packet, Simulator simulator)
+ {
+ if (OnAssetReceived != null)
+ {
+ TransferInfoPacket info = (TransferInfoPacket)packet;
+
+ if (Transfers.ContainsKey(info.TransferInfo.TransferID))
+ {
+ AssetDownload transfer = (AssetDownload)Transfers[info.TransferInfo.TransferID];
+
+ transfer.Channel = (ChannelType)info.TransferInfo.ChannelType;
+ transfer.Status = (StatusCode)info.TransferInfo.Status;
+ transfer.Target = (TargetType)info.TransferInfo.TargetType;
+ transfer.Size = info.TransferInfo.Size;
+
+ // TODO: Once we support mid-transfer status checking and aborting this
+ // will need to become smarter
+ if (transfer.Status != StatusCode.OK)
+ {
+ Client.Log("Transfer failed with status code " + transfer.Status, Helpers.LogLevel.Warning);
+
+ lock (Transfers) Transfers.Remove(transfer.ID);
+
+ // No data could have been received before the TransferInfo packet
+ transfer.AssetData = null;
+
+ // Fire the event with our transfer that contains Success = false;
+ try { OnAssetReceived(transfer); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+ else
+ {
+ transfer.AssetData = new byte[transfer.Size];
+
+ if (transfer.Source == SourceType.Asset && info.TransferInfo.Params.Length == 20)
+ {
+ transfer.AssetID = new LLUUID(info.TransferInfo.Params, 0);
+ AssetType type = (AssetType)(int)Helpers.BytesToUInt(info.TransferInfo.Params, 16);
+
+ //Client.DebugLog(String.Format("TransferInfo packet received. AssetID: {0} Type: {1}",
+ // transfer.AssetID, type));
+ }
+ else if (transfer.Source == SourceType.SimInventoryItem && info.TransferInfo.Params.Length == 100)
+ {
+ // TODO: Can we use these?
+ LLUUID agentID = new LLUUID(info.TransferInfo.Params, 0);
+ LLUUID sessionID = new LLUUID(info.TransferInfo.Params, 16);
+ LLUUID ownerID = new LLUUID(info.TransferInfo.Params, 32);
+ LLUUID taskID = new LLUUID(info.TransferInfo.Params, 48);
+ LLUUID itemID = new LLUUID(info.TransferInfo.Params, 64);
+ transfer.AssetID = new LLUUID(info.TransferInfo.Params, 80);
+ AssetType type = (AssetType)(int)Helpers.BytesToUInt(info.TransferInfo.Params, 96);
+
+ //Client.DebugLog(String.Format("TransferInfo packet received. AgentID: {0} SessionID: {1} " +
+ // "OwnerID: {2} TaskID: {3} ItemID: {4} AssetID: {5} Type: {6}", agentID, sessionID,
+ // ownerID, taskID, itemID, transfer.AssetID, type));
+ }
+ else
+ {
+ Client.Log("Received a TransferInfo packet with a SourceType of " + transfer.Source.ToString() +
+ " and a Params field length of " + info.TransferInfo.Params.Length,
+ Helpers.LogLevel.Warning);
+ }
+ }
+ }
+ else
+ {
+ Client.Log("Received a TransferInfo packet for an asset we didn't request, TransferID: " +
+ info.TransferInfo.TransferID, Helpers.LogLevel.Warning);
+ }
+ }
+ }
+
+ private void TransferPacketHandler(Packet packet, Simulator simulator)
+ {
+ TransferPacketPacket asset = (TransferPacketPacket)packet;
+
+ if (Transfers.ContainsKey(asset.TransferData.TransferID))
+ {
+ AssetDownload transfer = (AssetDownload)Transfers[asset.TransferData.TransferID];
+
+ if (transfer.Size == 0)
+ {
+ Client.DebugLog("TransferPacket received ahead of the transfer header, blocking...");
+
+ // We haven't received the header yet, block until it's received or times out
+ transfer.HeaderReceivedEvent.WaitOne(1000 * 20, false);
+
+ if (transfer.Size == 0)
+ {
+ Client.Log("Timed out while waiting for the asset header to download for " +
+ transfer.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
+
+ // Abort the transfer
+ TransferAbortPacket abort = new TransferAbortPacket();
+ abort.TransferInfo.ChannelType = (int)transfer.Channel;
+ abort.TransferInfo.TransferID = transfer.ID;
+ Client.Network.SendPacket(abort, transfer.Simulator);
+
+ transfer.Success = false;
+ lock (Transfers) Transfers.Remove(transfer.ID);
+
+ // Fire the event with our transfer that contains Success = false
+ if (OnAssetReceived != null)
+ {
+ try { OnAssetReceived(transfer); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+
+ return;
+ }
+ }
+
+ // This assumes that every transfer packet except the last one is exactly 1000 bytes,
+ // hopefully that is a safe assumption to make
+ Buffer.BlockCopy(asset.TransferData.Data, 0, transfer.AssetData, 1000 * asset.TransferData.Packet,
+ asset.TransferData.Data.Length);
+ transfer.Transferred += asset.TransferData.Data.Length;
+
+ //Client.DebugLog(String.Format("Transfer packet {0}, received {1}/{2}/{3} bytes for asset {4}",
+ // asset.TransferData.Packet, asset.TransferData.Data.Length, transfer.Transferred, transfer.Size,
+ // transfer.AssetID.ToStringHyphenated()));
+
+ // Check if we downloaded the full asset
+ if (transfer.Transferred >= transfer.Size)
+ {
+ Client.DebugLog("Transfer for asset " + transfer.AssetID.ToStringHyphenated() + " completed");
+
+ transfer.Success = true;
+ lock (Transfers) Transfers.Remove(transfer.ID);
+
+ if (OnAssetReceived != null)
+ {
+ try { OnAssetReceived(transfer); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+ }
+ }
+ else
+ {
+ //Client.DebugLog("Received a TransferPacket for unknown transfer " +
+ // asset.TransferData.TransferID.ToStringHyphenated());
+ }
+ }
+
+ private void RequestXferHandler(Packet packet, Simulator simulator)
+ {
+ AssetUpload upload = null;
+ RequestXferPacket request = (RequestXferPacket)packet;
+
+ // The Xfer system sucks. This will thankfully die soon when uploads are
+ // moved to HTTP
+ lock (Transfers)
+ {
+ // Associate the XferID with an upload. If an upload is initiated
+ // before the previous one is associated with an XferID one or both
+ // of them will undoubtedly fail
+ foreach (Transfer transfer in Transfers.Values)
+ {
+ if (transfer is AssetUpload)
+ {
+ if (((AssetUpload)transfer).XferID == 0)
+ {
+ // First match, use it
+ upload = (AssetUpload)transfer;
+ upload.XferID = request.XferID.ID;
+ upload.Type = (AssetType)request.XferID.VFileType;
+
+ // Remove this upload from the Transfers dictionary and re-insert
+ // it using the transferID as the key instead of the assetID
+ Transfers.Remove(upload.AssetID);
+
+ LLUUID transferID = new LLUUID(upload.XferID);
+ Transfers[transferID] = upload;
+
+ // Send the first packet containing actual asset data
+ SendNextUploadPacket(upload);
+
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ private void ConfirmXferPacketHandler(Packet packet, Simulator simulator)
+ {
+ ConfirmXferPacketPacket confirm = (ConfirmXferPacketPacket)packet;
+
+ // Building a new UUID every time an ACK is received for an upload is a horrible
+ // thing, but this whole Xfer system is horrible
+ LLUUID transferID = new LLUUID(confirm.XferID.ID);
+ AssetUpload upload = null;
+
+ lock (Transfers)
+ {
+ if (Transfers.ContainsKey(transferID))
+ {
+ upload = (AssetUpload)Transfers[transferID];
+
+ //Client.DebugLog(String.Format("ACK for upload {0} of asset type {1} ({2}/{3})",
+ // upload.AssetID.ToStringHyphenated(), upload.Type, upload.Transferred, upload.Size));
+
+ if (OnUploadProgress != null)
+ {
+ try { OnUploadProgress(upload); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+
+ if (upload.Transferred < upload.Size)
+ SendNextUploadPacket((AssetUpload)Transfers[transferID]);
+ }
+ }
+ }
+
+ private void AssetUploadCompleteHandler(Packet packet, Simulator simulator)
+ {
+ AssetUploadCompletePacket complete = (AssetUploadCompletePacket)packet;
+
+ if (OnAssetUploaded != null)
+ {
+ //Client.DebugLog(complete.ToString());
+
+ bool found = false;
+ KeyValuePair foundTransfer = new KeyValuePair();
+
+ // Xfer system sucks really really bad. Where is the damn XferID?
+ lock (Transfers)
+ {
+ foreach (KeyValuePair transfer in Transfers)
+ {
+ if (transfer.Value.GetType() == typeof(AssetUpload))
+ {
+ AssetUpload upload = (AssetUpload)transfer.Value;
+
+ if ((upload).AssetID == complete.AssetBlock.UUID)
+ {
+ found = true;
+ foundTransfer = transfer;
+ upload.Success = complete.AssetBlock.Success;
+ upload.Type = (AssetType)complete.AssetBlock.Type;
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (found)
+ {
+ lock (Transfers) Transfers.Remove(foundTransfer.Key);
+
+ try { OnAssetUploaded((AssetUpload)foundTransfer.Value); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+ }
+ }
+
+ ///
+ /// Handles the Image Data packet which includes the ID and Size of the image,
+ /// along with the first block of data for the image. If the image is small enough
+ /// there will be no additional packets
+ ///
+ public void ImageDataHandler(Packet packet, Simulator simulator)
+ {
+ ImageDataPacket data = (ImageDataPacket)packet;
+ ImageDownload transfer = null;
+
+ lock (Transfers)
+ {
+ if (Transfers.ContainsKey(data.ImageID.ID))
+ {
+ transfer = (ImageDownload)Transfers[data.ImageID.ID];
+
+ //Client.DebugLog("Received first " + data.ImageData.Data.Length + " bytes for image " +
+ // data.ImageID.ID.ToStringHyphenated());
+
+ transfer.Codec = data.ImageID.Codec;
+ transfer.PacketCount = data.ImageID.Packets;
+ transfer.Size = (int)data.ImageID.Size;
+ transfer.AssetData = new byte[transfer.Size];
+ Buffer.BlockCopy(data.ImageData.Data, 0, transfer.AssetData, 0, data.ImageData.Data.Length);
+ transfer.InitialDataSize = data.ImageData.Data.Length;
+ transfer.Transferred += data.ImageData.Data.Length;
+
+ // Check if we downloaded the full image
+ if (transfer.Transferred >= transfer.Size)
+ {
+ Transfers.Remove(transfer.ID);
+ transfer.Success = true;
+ }
+ }
+ }
+
+ if (transfer != null)
+ {
+ transfer.HeaderReceivedEvent.Set();
+
+ if (OnImageReceived != null && transfer.Transferred >= transfer.Size)
+ {
+ try { OnImageReceived(transfer); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+ }
+ }
+
+ ///
+ /// Handles the remaining Image data that did not fit in the initial ImageData packet
+ ///
+ public void ImagePacketHandler(Packet packet, Simulator simulator)
+ {
+ ImagePacketPacket image = (ImagePacketPacket)packet;
+ ImageDownload transfer = null;
+
+ lock (Transfers)
+ {
+ if (Transfers.ContainsKey(image.ImageID.ID))
+ {
+ transfer = (ImageDownload)Transfers[image.ImageID.ID];
+
+ if (transfer.Size == 0)
+ {
+ // We haven't received the header yet, block until it's received or times out
+ transfer.HeaderReceivedEvent.WaitOne(1000 * 20, false);
+
+ if (transfer.Size == 0)
+ {
+ Client.Log("Timed out while waiting for the image header to download for " +
+ transfer.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
+
+ transfer.Success = false;
+ Transfers.Remove(transfer.ID);
+ goto Callback;
+ }
+ }
+
+ // The header is downloaded, we can insert this data in to the proper position
+ Array.Copy(image.ImageData.Data, 0, transfer.AssetData, transfer.InitialDataSize +
+ (1000 * (image.ImageID.Packet - 1)), image.ImageData.Data.Length);
+ transfer.Transferred += image.ImageData.Data.Length;
+
+ //Client.DebugLog("Received " + image.ImageData.Data.Length + "/" + transfer.Transferred +
+ // "/" + transfer.Size + " bytes for image " + image.ImageID.ID.ToStringHyphenated());
+
+ // Check if we downloaded the full image
+ if (transfer.Transferred >= transfer.Size)
+ {
+ transfer.Success = true;
+ Transfers.Remove(transfer.ID);
+ }
+ }
+ }
+
+ Callback:
+
+ if (transfer != null && OnImageReceived != null && (transfer.Transferred >= transfer.Size || transfer.Size == 0))
+ {
+ try { OnImageReceived(transfer); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+ }
+
+ ///
+ /// The requested image does not exist on the asset server
+ ///
+ public void ImageNotInDatabaseHandler(Packet packet, Simulator simulator)
+ {
+ ImageNotInDatabasePacket notin = (ImageNotInDatabasePacket)packet;
+ ImageDownload transfer = null;
+
+ lock (Transfers)
+ {
+ if (Transfers.ContainsKey(notin.ImageID.ID))
+ {
+ transfer = (ImageDownload)Transfers[notin.ImageID.ID];
+ transfer.NotFound = true;
+ Transfers.Remove(transfer.ID);
+ }
+ }
+
+ // Fire the event with our transfer that contains Success = false;
+ if (transfer != null && OnImageReceived != null)
+ {
+ try { OnImageReceived(transfer); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+ }
+ }
+}
diff --git a/libsecondlife/EstateTools.cs b/libsecondlife/EstateTools.cs
index 37b3fe49..2bdad73e 100644
--- a/libsecondlife/EstateTools.cs
+++ b/libsecondlife/EstateTools.cs
@@ -35,7 +35,6 @@ namespace libsecondlife
public class EstateTools
{
private SecondLife Client;
- public TerrainTextureSettings TerrainTextures;
///
/// Triggered on incoming LandStatReply
@@ -73,7 +72,6 @@ namespace libsecondlife
{
Client = client;
Client.Network.RegisterCallback(PacketType.LandStatReply, new NetworkManager.PacketCallback(LandStatReplyHandler));
- //Client.Network.RegisterCallback(PacketType.EstateOwnerMessage, new NetworkManager.PacketCallback(EstateOwnerMessageHandler));
}
/// Describes tasks returned in LandStatReply
@@ -94,36 +92,6 @@ namespace libsecondlife
TopColliders = 1
}
- /// Used by EstateOwnerMessage packets
- public enum EstateAccessDelta
- {
- BanUser = 64,
- UnbanUser = 128
- }
-
- /// Used by TerrainTextureSettings
- public class TerrainTextureRegion
- {
- public LLUUID TextureID;
- public float Low;
- public float High;
- TerrainTextureRegion()
- {
- TextureID = LLUUID.Zero;
- Low = 0f;
- High = 0f;
- }
- }
-
- /// Texture settings for each corner of the region
- public class TerrainTextureSettings
- {
- public TerrainTextureRegion Southwest;
- public TerrainTextureRegion Northwest;
- public TerrainTextureRegion Southeast;
- public TerrainTextureRegion Northeast;
- }
-
///
/// Requests estate information such as top scripts and colliders
///
@@ -146,36 +114,17 @@ namespace libsecondlife
/// Requests the "Top Scripts" list for the current region
public void GetTopScripts()
{
- //EstateOwnerMessage("scripts", "");
LandStatRequest(0, LandStatReportType.TopScripts, 0, "");
}
/// Requests the "Top Colliders" list for the current region
public void GetTopColliders()
{
- //EstateOwnerMessage("colliders", "");
LandStatRequest(0, LandStatReportType.TopColliders, 0, "");
}
- ///
- ///
- ///
- private void EstateOwnerMessageHandler(Packet packet, Simulator simulator)
- {
- EstateOwnerMessagePacket message = (EstateOwnerMessagePacket)packet;
- string method = Helpers.FieldToUTF8String(message.MethodData.Method);
-
- //FIXME - remove debug output
- Console.WriteLine("--- " + method + " ---");
- foreach (EstateOwnerMessagePacket.ParamListBlock block in message.ParamList)
- {
- Console.WriteLine(Helpers.FieldToUTF8String(block.Parameter));
- }
- Console.WriteLine("------");
-
- }
-
- ///
+ ///
+ ///
///
///
private void LandStatReplyHandler(Packet packet, Simulator simulator)
@@ -224,148 +173,54 @@ namespace libsecondlife
}
}
-
- public void EstateOwnerMessage(string method, string param)
- {
- List listParams = new List();
- listParams.Add(param);
- EstateOwnerMessage(method, listParams);
- }
+
///
- /// Used for setting and retrieving various estate panel settings
+ /// Kick an Avatar from an estate
///
- /// EstateOwnerMessage Method field
- /// List of parameters to include
- /// Use LLUUID.Random() for Invoice field instead of LLUUID.Zero
- public void EstateOwnerMessage(string method, ListlistParams)
- {
+ /// Key of Avatar to kick
+ public void KickUser(LLUUID prey)
+ {
EstateOwnerMessagePacket estate = new EstateOwnerMessagePacket();
estate.AgentData.AgentID = Client.Network.AgentID;
estate.AgentData.SessionID = Client.Network.SessionID;
estate.MethodData.Invoice = LLUUID.Random();
- estate.MethodData.Method = Helpers.StringToField(method);
- estate.ParamList = new EstateOwnerMessagePacket.ParamListBlock[listParams.Count];
- for (int i = 0; i < listParams.Count; i++)
- {
- estate.ParamList[i] = new EstateOwnerMessagePacket.ParamListBlock();
- estate.ParamList[i].Parameter = Helpers.StringToField(listParams[i]);
- }
- Client.Network.SendPacket((Packet)estate);
- }
+ estate.MethodData.Method = Helpers.StringToField("kick");
+ estate.ParamList = new EstateOwnerMessagePacket.ParamListBlock[2];
+ estate.ParamList[0].Parameter = Helpers.StringToField(Client.Network.AgentID.ToStringHyphenated());
+ estate.ParamList[1].Parameter = Helpers.StringToField(prey.ToStringHyphenated());
- ///
- /// Kick an avatar from an estate
- ///
- /// Key of Avatar to kick
- public void KickUser(LLUUID userID)
- {
- EstateOwnerMessage("kickestate", userID.ToStringHyphenated());
+ Client.Network.SendPacket((Packet)estate);
}
- /// Ban an avatar from an estate
- public void BanUser(LLUUID userID)
- {
- List listParams = new List();
- listParams.Add(Client.Network.AgentID.ToStringHyphenated());
- listParams.Add(EstateAccessDelta.BanUser.ToString());
- listParams.Add(userID.ToStringHyphenated());
- EstateOwnerMessage("estateaccessdelta", listParams);
- }
-
- /// Unban an avatar from an estate
- public void UnbanUser(LLUUID userID)
- {
- List listParams = new List();
- listParams.Add(Client.Network.AgentID.ToStringHyphenated());
- listParams.Add(EstateAccessDelta.UnbanUser.ToString());
- listParams.Add(userID.ToStringHyphenated());
- EstateOwnerMessage("estateaccessdelta", listParams);
- }
-
///
- /// Send a message dialog to everyone in an entire estate
+ /// Ban an Avatar from an estate
///
- /// Message to send all users in the estate
- public void EstateMessage(string message)
- {
- List listParams = new List();
- listParams.Add(Client.Self.FirstName + " " + Client.Self.LastName);
- listParams.Add(message);
- EstateOwnerMessage("instantmessage", listParams);
- }
+ /// Key of Avatar to ban
+ public void BanUser(LLUUID prey)
+ {
+ // FIXME:
+ //Client.Network.SendPacket(Packets.Estate.EstateBan(Client.Protocol,Client.Avatar.ID,Client.Network.SessionID,prey));
+ }
///
- /// Send a message dialog to everyone in a simulator
- ///
- /// Message to send all users in the simulator
- public void SimulatorMessage(string message)
- {
- List listParams = new List();
- listParams.Add("-1");
- listParams.Add("-1");
- listParams.Add(Client.Network.AgentID.ToStringHyphenated());
- listParams.Add(Client.Self.FirstName + " " + Client.Self.LastName);
- listParams.Add(message);
- EstateOwnerMessage("simulatormessage", listParams);
- }
-
- ///
- /// Send an avatar back to their home location
- ///
- /// Key of avatar to send home
- public void TeleportHomeUser(LLUUID pest)
- {
- List listParams = new List();
- listParams.Add(Client.Network.AgentID.ToStringHyphenated());
- listParams.Add(pest.ToStringHyphenated());
- EstateOwnerMessage("teleporthomeuser", listParams);
- }
-
- ///
- /// Begin the region restart process
+ ///
///
///
- public void RestartRegion()
- {
- EstateOwnerMessage("restart", "120");
- }
+ public void UnBanUser(LLUUID prey)
+ {
+ // FIXME:
+ //Client.Network.SendPacket(Packets.Estate.EstateUnBan(Client.Protocol,Client.Avatar.ID,Client.Network.SessionID,prey));
+ }
///
- /// Cancels a region restart
+ ///
///
///
- public void CancelRestart()
- {
- EstateOwnerMessage("restart", "-1");
- }
-
- /// Estate panel "Region" tab settings
- public void SetRegionInfo(bool blockTerraform, bool blockFly, bool allowDamage, bool allowLandResell, bool restrictPushing, bool allowParcelJoinDivide, float agentLimit, float objectBonus, bool mature)
- {
- List listParams = new List();
- if (blockTerraform) listParams.Add("Y"); else listParams.Add("N");
- if (blockFly) listParams.Add("Y"); else listParams.Add("N");
- if (allowDamage) listParams.Add("Y"); else listParams.Add("N");
- if (allowLandResell) listParams.Add("Y"); else listParams.Add("N");
- listParams.Add(agentLimit.ToString());
- listParams.Add(objectBonus.ToString());
- if (mature) listParams.Add("21"); else listParams.Add("13"); //FIXME - enumerate these settings
- if (restrictPushing) listParams.Add("Y"); else listParams.Add("N");
- if (allowParcelJoinDivide) listParams.Add("Y"); else listParams.Add("N");
- EstateOwnerMessage("setregioninfo", listParams);
- }
-
- /// Estate panel "Debug" tab settings
- public void SetRegionDebug(bool disableScripts, bool disableCollisions, bool disablePhysics)
- {
- List listParams = new List();
- if (disableScripts) listParams.Add("Y"); else listParams.Add("N");
- if (disableCollisions) listParams.Add("Y"); else listParams.Add("N");
- if (disablePhysics) listParams.Add("Y"); else listParams.Add("N");
- EstateOwnerMessage("setregiondebug", listParams);
- }
-
+ public void TeleportHomeUser(LLUUID prey)
+ {
+ // FIXME:
+ //Client.Network.SendPacket(Packets.Estate.EstateTeleportUser(Client.Protocol,Client.Avatar.ID,Client.Network.SessionID,prey));
+ }
}
-
}
diff --git a/libsecondlife/FriendManager.cs b/libsecondlife/FriendManager.cs
index 279c4fad..2feb7ba4 100644
--- a/libsecondlife/FriendManager.cs
+++ b/libsecondlife/FriendManager.cs
@@ -80,10 +80,8 @@ namespace libsecondlife
p.AgentData.AgentID = Client.Network.AgentID;
p.AgentData.SessionID = Client.Network.SessionID;
p.Rights = new GrantUserRightsPacket.RightsBlock[1];
- p.Rights[0] = new GrantUserRightsPacket.RightsBlock();
- p.Rights[0].AgentRelated = targetID;
- p.Rights[0].RelatedRights = (int)rights;
- Client.Network.SendPacket(p);
+ p.Rights[1].AgentRelated = targetID;
+ p.Rights[1].RelatedRights = (int)rights;
}
///
diff --git a/libsecondlife/Inventory.cs b/libsecondlife/Inventory.cs
new file mode 100644
index 00000000..de561e43
--- /dev/null
+++ b/libsecondlife/Inventory.cs
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2007, 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.Generic;
+
+namespace libsecondlife
+{
+ ///
+ /// Responsible for maintaining inventory structure. Inventory constructs nodes
+ /// and manages node children as is necessary to maintain a coherant hirarchy.
+ /// Other classes should not manipulate or create InventoryNodes explicitly. When
+ /// A node's parent changes (when a folder is moved, for example) simply pass
+ /// Inventory the updated InventoryFolder and it will make the appropriate changes
+ /// to its internal representation.
+ ///
+ public class Inventory
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ public delegate void InventoryObjectUpdated(InventoryObject oldObject, InventoryObject newObject);
+
+ ///
+ ///
+ ///
+ public event InventoryObjectUpdated OnInventoryObjectUpdated;
+
+ ///
+ ///
+ ///
+ public readonly InventoryFolder RootFolder;
+ ///
+ ///
+ ///
+ public readonly InventoryNode RootNode;
+
+ private SecondLife Client;
+ private InventoryManager Manager;
+ private Dictionary Items;
+
+ ///
+ /// By using the bracket operator on this class, the program can get the
+ /// InventoryObject designated by the specified uuid. If the value for the corresponding
+ /// UUID is null, the call is equivelant to a call to RemoveNodeFor(this[uuid]).
+ /// If the value is non-null, it is equivelant to a call to UpdateNodeFor(value),
+ /// the uuid parameter is ignored.
+ ///
+ /// The UUID of the InventoryObject to get or set, ignored if set to non-null value.
+ /// The InventoryObject corresponding to uuid.
+ public InventoryObject this[LLUUID uuid]
+ {
+ get
+ {
+ InventoryNode node = Items[uuid];
+ return node.Data;
+ }
+ set
+ {
+ if (value != null)
+ {
+ // what if value.UUID != uuid? :-O
+ // should we check for this?
+ UpdateNodeFor(value);
+ }
+ else
+ {
+ InventoryNode node;
+ if (Items.TryGetValue(uuid, out node))
+ {
+ RemoveNodeFor(node.Data);
+ }
+ }
+ }
+ }
+
+ public Inventory(SecondLife client, InventoryManager manager, InventoryFolder rootFolder)
+ {
+ Client = client;
+ Manager = manager;
+ RootFolder = rootFolder;
+ RootNode = new InventoryNode(rootFolder);
+ Items = new Dictionary();
+ Items[rootFolder.UUID] = RootNode;
+ }
+
+
+ public List GetContents(InventoryFolder folder)
+ {
+ return GetContents(folder.UUID);
+ }
+
+ ///
+ /// Returns the contents of the specified folder.
+ ///
+ /// A folder's UUID.
+ /// The contents of the folder corresponding to folder.
+ /// When folder does not exist in the inventory.
+ public List GetContents(LLUUID folder)
+ {
+ InventoryNode folderNode;
+ if (!Items.TryGetValue(folder, out folderNode))
+ throw new InventoryException("Unknown folder: " + folder);
+ lock (folderNode.Nodes.SyncRoot)
+ {
+ List contents = new List(folderNode.Nodes.Count);
+ foreach (InventoryNode node in folderNode.Nodes.Values)
+ {
+ contents.Add(node.Data);
+ }
+ return contents;
+ }
+ }
+
+
+ ///
+ /// Updates the state of the InventoryNode and inventory data structure that
+ /// is responsible for the InventoryObject. If the item was previously not added to inventory,
+ /// it adds the item, and updates structure accordingly. If it was, it updates the
+ /// InventoryNode, changing the parent node if item.parentUUID does
+ /// not match node.Parent.Data.UUID.
+ ///
+ /// You can not set the inventory root folder using this method.
+ ///
+ /// The InventoryObject to store.
+ public void UpdateNodeFor(InventoryObject item)
+ {
+ lock (Items)
+ {
+ if (item.ParentUUID == LLUUID.Zero)
+ {
+ Client.Log("UpdateNodeFor(): Cannot add the root folder", Helpers.LogLevel.Warning);
+ return;
+ }
+
+ InventoryNode itemParent;
+ if (!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.
+ itemParent = new InventoryNode(fakeParent);
+ Items[item.ParentUUID] = itemParent;
+ // Unfortunately, this breaks the nice unified tree
+ // while we're waiting for the parent's data to come in.
+ // As soon as we get the parent, the tree repairs itself.
+ Client.DebugLog("Attempting to update inventory child of " +
+ item.ParentUUID.ToStringHyphenated() +
+ " when we have no local reference to that folder");
+
+ if (Client.Settings.FETCH_MISSING_INVENTORY)
+ {
+ // Fetch the parent
+ List fetchreq = new List(1);
+ fetchreq.Add(item.ParentUUID);
+ Manager.FetchInventory(fetchreq);
+ }
+ }
+
+ InventoryNode itemNode;
+ if (Items.TryGetValue(item.UUID, out itemNode)) // We're updating.
+ {
+ InventoryNode oldParent = itemNode.Parent;
+ // Handle parent change
+ if (oldParent != null && itemParent.Data.UUID != oldParent.Data.UUID)
+ {
+ lock (oldParent.Nodes.SyncRoot)
+ oldParent.Nodes.Remove(item.UUID);
+
+ lock (itemParent.Nodes.SyncRoot)
+ itemParent.Nodes[item.UUID] = itemNode;
+ }
+
+ itemNode.Parent = itemParent;
+
+ if (item != itemNode.Data)
+ FireOnInventoryObjectUpdated(itemNode.Data, item);
+
+ itemNode.Data = item;
+ }
+ else // We're adding.
+ {
+ itemNode = new InventoryNode(item, itemParent);
+ Items.Add(item.UUID, itemNode);
+ }
+ }
+ }
+
+ ///
+ /// Removes the InventoryObject and all related node data from Inventory.
+ ///
+ /// The InventoryObject to remove.
+ public void RemoveNodeFor(InventoryObject item)
+ {
+ lock (Items)
+ {
+ InventoryNode node;
+ if (Items.TryGetValue(item.UUID, out node))
+ {
+ if (node.Parent != null)
+ lock (node.Parent.Nodes.SyncRoot)
+ node.Parent.Nodes.Remove(item.UUID);
+ Items.Remove(item.UUID);
+ }
+
+ // In case there's a new parent:
+ InventoryNode newParent;
+ if (Items.TryGetValue(item.ParentUUID, out newParent))
+ {
+ lock (newParent.Nodes.SyncRoot)
+ newParent.Nodes.Remove(item.UUID);
+ }
+ }
+ }
+
+ ///
+ /// Used to find out if Inventory contains the InventoryObject
+ /// specified by uuid.
+ ///
+ /// The LLUUID to check.
+ /// true if inventory contains uuid, false otherwise
+ public bool Contains(LLUUID uuid)
+ {
+ return Items.ContainsKey(uuid);
+ }
+
+ public bool Contains(InventoryObject obj)
+ {
+ return Contains(obj.UUID);
+ }
+
+ #region Event Firing
+ protected void FireOnInventoryObjectUpdated(InventoryObject oldObject, InventoryObject newObject)
+ {
+ if (OnInventoryObjectUpdated != null)
+ OnInventoryObjectUpdated(oldObject, newObject);
+ }
+ #endregion
+
+ }
+
+ ///
+ /// A rudimentary Exception subclass, so exceptions thrown by the Inventory class
+ /// can be easily identified and caught.
+ ///
+ public class InventoryException : Exception
+ {
+ public InventoryException(string message)
+ : base(message) { }
+ }
+}
diff --git a/libsecondlife/InventoryManager.cs b/libsecondlife/InventoryManager.cs
new file mode 100644
index 00000000..97398900
--- /dev/null
+++ b/libsecondlife/InventoryManager.cs
@@ -0,0 +1,1068 @@
+/*
+ * Copyright (c) 2006-2007, 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.Generic;
+using System.Text.RegularExpressions;
+using System.Threading;
+using libsecondlife;
+using libsecondlife.Packets;
+
+namespace libsecondlife
+{
+ public enum InventoryType : sbyte
+ {
+ Unknown = -1,
+ Texture = 0,
+ Sound = 1,
+ CallingCard = 2,
+ Landmark = 3,
+ [Obsolete]
+ Script = 4,
+ [Obsolete]
+ Clothing = 5,
+ Object = 6,
+ Notecard = 7,
+ Category = 8,
+ Folder = 8,
+ RootCategory = 0,
+ LSL = 10,
+ [Obsolete]
+ LSLBytecode = 11,
+ [Obsolete]
+ TextureTGA = 12,
+ [Obsolete]
+ Bodypart = 13,
+ [Obsolete]
+ Trash = 14,
+ Snapshot = 15,
+ [Obsolete]
+ LostAndFound = 16,
+ Attachment = 17,
+ Wearable = 18,
+ Animation = 19,
+ Gesture = 20
+ }
+
+ [Flags]
+ public enum InventorySortOrder : int
+ {
+ /// Sort by name
+ ByName = 0,
+ /// Sort by date
+ ByDate = 1,
+ /// Sort folders by name, regardless of whether items are
+ /// sorted by name or date
+ FoldersByName = 2,
+ /// Place system folders at the top
+ SystemFoldersToTop = 4
+ }
+
+ public abstract class InventoryObject
+ {
+ public readonly LLUUID UUID;
+ public LLUUID ParentUUID;
+ public string Name;
+ public LLUUID OwnerID;
+
+ public InventoryObject(LLUUID itemID)
+ {
+ if (itemID == LLUUID.Zero)
+ throw new ArgumentException("Inventory item ID cannot be NULL_KEY (LLUUID.Zero)");
+ UUID = itemID;
+ }
+
+ public override int GetHashCode()
+ {
+ return UUID.GetHashCode() ^ ParentUUID.GetHashCode() ^ Name.GetHashCode() ^ OwnerID.GetHashCode();
+ }
+
+ public override bool Equals(object o)
+ {
+ InventoryObject inv = o as InventoryObject;
+ return inv != null && Equals(inv);
+ }
+
+ public virtual bool Equals(InventoryObject o)
+ {
+ return o.UUID == UUID
+ && o.ParentUUID == ParentUUID
+ && o.Name == Name
+ && o.OwnerID == OwnerID;
+ }
+ }
+
+ public class InventoryItem : InventoryObject
+ {
+ public LLUUID AssetUUID;
+ public Permissions Permissions;
+ public AssetType AssetType;
+ public InventoryType InventoryType;
+ public string Description;
+ public LLUUID GroupID;
+ public bool GroupOwned;
+ public int SalePrice;
+ public SaleType SaleType;
+ public uint Flags;
+ /// Time and date this inventory item was created, stored as
+ /// UTC (Coordinated Universal Time)
+ public DateTime CreationDate;
+
+ public InventoryItem(LLUUID itemID)
+ : base(itemID) { }
+
+ 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();
+ }
+
+ public override bool Equals(object o)
+ {
+ InventoryItem item = o as InventoryItem;
+ return item != null && Equals(item);
+ }
+
+ public override bool Equals(InventoryObject o)
+ {
+ InventoryItem item = o as InventoryItem;
+ return item != null && Equals(item);
+ }
+
+ public bool Equals(InventoryItem o)
+ {
+ return base.Equals(o as InventoryObject)
+ && 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;
+ }
+ }
+
+ public class InventoryFolder : InventoryObject
+ {
+ public AssetType PreferredType;
+ public int Version;
+ public int DescendentCount;
+
+ public InventoryFolder(LLUUID itemID)
+ : base(itemID) { }
+
+ public override int GetHashCode()
+ {
+ return PreferredType.GetHashCode() ^ Version.GetHashCode() ^ DescendentCount.GetHashCode();
+ }
+
+ public override bool Equals(object o)
+ {
+ InventoryFolder folder = o as InventoryFolder;
+ return folder != null && Equals(folder);
+ }
+
+ public override bool Equals(InventoryObject o)
+ {
+ InventoryFolder folder = o as InventoryFolder;
+ return folder != null && Equals(folder);
+ }
+
+ public bool Equals(InventoryFolder o)
+ {
+ return base.Equals(o as InventoryObject)
+ && o.DescendentCount == DescendentCount
+ && o.PreferredType == PreferredType
+ && o.Version == Version;
+ }
+ }
+
+ public class InventoryManager
+ {
+ public delegate void InventoryFolderUpdated(LLUUID folderID);
+ public delegate bool InventoryObjectReceived(LLUUID fromAgentID, string fromAgentName, uint parentEstateID,
+ LLUUID regionID, LLVector3 position, DateTime timestamp, AssetType type, LLUUID objectID, bool fromTask);
+
+ public event InventoryFolderUpdated OnInventoryFolderUpdated;
+ public event InventoryObjectReceived OnInventoryObjectReceived;
+
+ private SecondLife Client;
+ public Inventory Store
+ {
+ get { return store; }
+ }
+
+ private Inventory store;
+
+ /// Partial mapping of AssetTypes to folder names
+ private string[] NewFolderNames = new string[]
+ {
+ "Textures",
+ "Sounds",
+ "Calling Cards",
+ "Landmarks",
+ "Scripts",
+ "Clothing",
+ "Objects",
+ "Notecards",
+ "New Folder",
+ "Inventory",
+ "Scripts",
+ "Scripts",
+ "Uncompressed Images",
+ "Body Parts",
+ "Trash",
+ "Photo Album",
+ "Lost And Found",
+ "Uncompressed Sounds",
+ "Uncompressed Images",
+ "Uncompressed Images",
+ "Animations",
+ "Gestures"
+ };
+
+ public InventoryManager(SecondLife client)
+ {
+ Client = client;
+
+ Client.Network.RegisterCallback(PacketType.UpdateCreateInventoryItem, new NetworkManager.PacketCallback(UpdateCreateInventoryItemHandler));
+ //Client.Network.RegisterCallback(PacketType.SaveAssetIntoInventory, new NetworkManager.PacketCallback(SaveAssetIntoInventoryHandler));
+ Client.Network.RegisterCallback(PacketType.BulkUpdateInventory, new NetworkManager.PacketCallback(BulkUpdateInventoryHandler));
+ //Client.Network.RegisterCallback(PacketType.MoveInventoryItem, new NetworkManager.PacketCallback(MoveInventoryItemHandler));
+ //Client.Network.RegisterCallback(PacketType.MoveInventoryFolder, new NetworkManager.PacketCallback(MoveInventoryFolderHandler));
+ Client.Network.RegisterCallback(PacketType.InventoryDescendents, new NetworkManager.PacketCallback(InventoryDescendentsHandler));
+ Client.Network.RegisterCallback(PacketType.FetchInventoryReply, new NetworkManager.PacketCallback(FetchInventoryReplyHandler));
+ // Watch for inventory given to us through instant message
+ Client.Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(Self_OnInstantMessage);
+ }
+
+ #region Searching
+ private Dictionary> folderRequests = new Dictionary>();
+
+ ///
+ /// Starts a search for any items whose names match the regex within
+ /// the spacified folder.
+ ///
+ /// Use the AsyncWaitHandle of the returned value to run the search synchronously.
+ /// The UUID of the folder to look in.
+ /// The regex that results match.
+ /// Recurse into and search inside subfolders of baseFolder.
+ /// Re-download the contents of baseFolder (and its subdirectories, if recursing)
+ /// The AsyncCallback to call when the search is complete.
+ /// An object that will be passed back to the caller.
+ /// An IAsyncResult that represents this find operation, and can be passed to EndFindObjects.
+ public IAsyncResult BeginFindObjects(LLUUID baseFolder, string regex, bool recurse, bool refresh, AsyncCallback callback, object asyncState)
+ {
+ return BeginFindObjects(baseFolder, new Regex(regex), recurse, refresh, callback, asyncState);
+ }
+
+ public IAsyncResult BeginFindObjects(LLUUID baseFolder, Regex regexp, bool recurse, bool refresh, AsyncCallback callback, object asyncState)
+ {
+ FindResult result = new FindResult(regexp, recurse, callback);
+ result.AsyncState = asyncState;
+ if (refresh)
+ {
+ result.FoldersWaiting = 1; // waiting for baseFolder.
+ lock (folderRequests)
+ {
+ if (!folderRequests.ContainsKey(baseFolder))
+ {
+ folderRequests.Add(baseFolder, new List());
+ }
+ List reqsForFolder = folderRequests[baseFolder];
+ reqsForFolder.Add(result);
+ RequestFolderContents(baseFolder, Client.Network.AgentID, true, true, InventorySortOrder.ByName);
+ }
+ }
+ else
+ {
+ result.Result = LocalFind(baseFolder, regexp, recurse);
+ result.CompletedSynchronously = true;
+ result.IsCompleted = true;
+ }
+ return result;
+ }
+
+ public List EndFindObjects(IAsyncResult result)
+ {
+ if (result is FindResult)
+ {
+ FindResult fr = result as FindResult;
+ if (fr.IsCompleted)
+ {
+ return fr.Result;
+ }
+ else
+ {
+ fr.AsyncWaitHandle.WaitOne();
+ return fr.Result;
+ }
+ }
+ else
+ {
+ throw new Exception("EndFindObjects must be passed the return value of BeginFindObjects.");
+ }
+ }
+
+ private void HandleDescendantsForFind(LLUUID refreshedFolder)
+ {
+ lock (folderRequests)
+ {
+ // Get the finds that requested this folder.
+ List requests = folderRequests[refreshedFolder];
+ foreach (FindResult req in requests)
+ {
+ Interlocked.Decrement(ref req.FoldersWaiting);
+ List contents = Store.GetContents(refreshedFolder);
+ foreach (InventoryObject obj in contents)
+ {
+ if (req.Regex.IsMatch(obj.Name))
+ {
+ req.Result.Add(obj);
+ }
+
+ if (obj is InventoryFolder && req.Recurse)
+ {
+ Interlocked.Increment(ref req.FoldersWaiting);
+ if (!folderRequests.ContainsKey(obj.UUID))
+ {
+ folderRequests.Add(obj.UUID, new List());
+ }
+ List reqsForFolder = folderRequests[obj.UUID];
+ reqsForFolder.Add(req);
+ RequestFolderContents(obj.UUID, Client.Network.AgentID, true, true, InventorySortOrder.ByName);
+ }
+ }
+ req.IsCompleted = (req.FoldersWaiting == 0);
+ }
+ // Satasfied all finds requesting this folder.
+ folderRequests.Remove(refreshedFolder);
+ }
+ }
+
+
+ private List LocalFind(LLUUID baseFolder, Regex regexp, bool recurse)
+ {
+ List objects = new List();
+ List folders = new List();
+
+ List contents = Store.GetContents(baseFolder);
+ foreach (InventoryObject inv in contents)
+ {
+ if (regexp.IsMatch(inv.Name))
+ {
+ objects.Add(inv);
+ }
+ if (inv is InventoryFolder)
+ {
+ folders.Add(inv as InventoryFolder);
+ }
+ }
+ // Recurse outside of the loop because subsequent calls to FindObjects may
+ // modify the baseNode.Nodes collection.
+ if (recurse)
+ {
+ foreach (InventoryFolder folder in folders)
+ {
+ objects.AddRange(LocalFind(folder.UUID, regexp, true));
+ }
+ }
+ return objects;
+ }
+ #endregion
+
+ #region Folder Actions
+
+ public void RequestFolderContents(LLUUID folder, LLUUID owner, bool folders, bool items,
+ InventorySortOrder order)
+ {
+ FetchInventoryDescendentsPacket fetch = new FetchInventoryDescendentsPacket();
+
+ fetch.AgentData.AgentID = Client.Network.AgentID;
+ fetch.AgentData.SessionID = Client.Network.SessionID;
+
+ fetch.InventoryData.FetchFolders = folders;
+ fetch.InventoryData.FetchItems = items;
+ fetch.InventoryData.FolderID = folder;
+ fetch.InventoryData.OwnerID = owner;
+ fetch.InventoryData.SortOrder = (int)order;
+
+ Client.Network.SendPacket(fetch);
+ }
+
+ ///
+ /// Returns the UUID of the folder (category) that defaults to
+ /// containing 'type'. The folder is not necessarily only for that
+ /// type
+ ///
+ /// This will create a new inventory folder on the fly if
+ /// one does not exist
+ ///
+ /// The UUID of the desired or newly created folder, or
+ /// LLUUID.Zero on failure
+ public LLUUID FindFolderForType(AssetType type)
+ {
+ if (Store == null)
+ {
+ Client.Log("Inventory is null, FindFolderForType() lookup cannot continue",
+ Helpers.LogLevel.Error);
+ return LLUUID.Zero;
+ }
+
+ // Folders go in the root
+ if (type == AssetType.Folder)
+ return Store.RootFolder.UUID;
+
+ // Loop through each top-level directory and check if PreferredType
+ // matches the requested type
+ List contents = Store.GetContents(Store.RootFolder.UUID);
+ foreach (InventoryObject inv in contents)
+ {
+ if (inv is InventoryFolder)
+ {
+ InventoryFolder folder = inv as InventoryFolder;
+
+ if (folder.PreferredType == type)
+ return folder.UUID;
+ }
+ }
+
+ // No match found, create one
+ return CreateFolder(Store.RootFolder.UUID, type, String.Empty);
+ }
+
+ public LLUUID CreateFolder(LLUUID parentID, AssetType preferredType, string name)
+ {
+ LLUUID id = LLUUID.Random();
+
+ // Assign a folder name if one is not already set
+ if (String.IsNullOrEmpty(name))
+ {
+ if (preferredType >= AssetType.Texture && preferredType <= AssetType.Gesture)
+ {
+ name = NewFolderNames[(int)preferredType];
+ }
+ else
+ {
+ name = "New Folder";
+ }
+ }
+
+ // Create the new folder locally
+ InventoryFolder newFolder = new InventoryFolder(id);
+ newFolder.Version = 1;
+ newFolder.DescendentCount = 0;
+ newFolder.ParentUUID = parentID;
+ newFolder.PreferredType = preferredType;
+ newFolder.Name = name;
+ newFolder.OwnerID = Client.Network.AgentID;
+
+ try
+ {
+ Store[newFolder.UUID] = newFolder;
+ }
+ catch (InventoryException ie)
+ {
+ Client.Log(ie.Message, Helpers.LogLevel.Warning);
+ }
+
+ // Create the create folder packet and send it
+ CreateInventoryFolderPacket create = new CreateInventoryFolderPacket();
+ create.AgentData.AgentID = Client.Network.AgentID;
+ create.AgentData.SessionID = Client.Network.SessionID;
+ create.FolderData.FolderID = id;
+ create.FolderData.ParentID = parentID;
+ create.FolderData.Type = (sbyte)preferredType;
+ create.FolderData.Name = Helpers.StringToField(name);
+
+ Client.Network.SendPacket(create);
+
+ return id;
+ }
+
+ public void RemoveDescendants(LLUUID folder)
+ {
+ PurgeInventoryDescendentsPacket purge = new PurgeInventoryDescendentsPacket();
+ purge.AgentData.AgentID = Client.Network.AgentID;
+ purge.AgentData.SessionID = Client.Network.SessionID;
+ purge.InventoryData.FolderID = folder;
+ Client.Network.SendPacket(purge);
+
+ // Update our local copy:
+ if (Store.Contains(folder))
+ {
+ List contents = Store.GetContents(folder);
+ foreach (InventoryObject obj in contents) {
+ Store.RemoveNodeFor(obj);
+ }
+ }
+ }
+
+ public void RemoveFolder(LLUUID folder)
+ {
+ List folders = new List(1);
+ folders.Add(folder);
+ Remove(null, folders);
+ }
+ #endregion Folder Actions
+
+ #region Item Actions
+ public void RemoveItem(LLUUID item)
+ {
+ List items = new List(1);
+ items.Add(item);
+ Remove(items, null);
+ }
+ #endregion
+
+ internal void InitializeRootNode(LLUUID rootFolderID)
+ {
+ InventoryFolder rootFolder = new InventoryFolder(rootFolderID);
+ rootFolder.Name = String.Empty;
+ rootFolder.ParentUUID = LLUUID.Zero;
+
+ store = new Inventory(Client, this, rootFolder);
+ }
+
+ ///
+ /// If you have a list of inventory item IDs (from a cached inventory, perhaps)
+ /// you can use this function to request an update from the server for those items.
+ ///
+ /// A list of LLUUIDs of the items to request.
+ public void FetchInventory(List itemIDs)
+ {
+ FetchInventoryPacket fetch = new FetchInventoryPacket();
+ fetch.AgentData = new FetchInventoryPacket.AgentDataBlock();
+ fetch.AgentData.AgentID = Client.Network.AgentID;
+ fetch.AgentData.SessionID = Client.Network.SessionID;
+
+ fetch.InventoryData = new FetchInventoryPacket.InventoryDataBlock[itemIDs.Count];
+ // TODO: Make sure the packet doesnt overflow.
+ for (int i = 0; i < itemIDs.Count; ++i)
+ {
+ fetch.InventoryData[i] = new FetchInventoryPacket.InventoryDataBlock();
+ fetch.InventoryData[i].ItemID = itemIDs[i];
+ fetch.InventoryData[i].OwnerID = Client.Network.AgentID;
+ }
+
+ Client.Network.SendPacket(fetch);
+ }
+
+ public void Remove(InventoryObject obj)
+ {
+ List temp = new List(1);
+ temp.Add(obj);
+ Remove(temp);
+ }
+
+ public void Remove(List objects)
+ {
+ List items = new List(objects.Count);
+ List folders = new List(objects.Count);
+ foreach (InventoryObject obj in objects)
+ {
+ if (obj is InventoryFolder)
+ {
+ folders.Add(obj.UUID);
+ }
+ else
+ {
+ items.Add(obj.UUID);
+ }
+ }
+ Remove(items, folders);
+ }
+
+ public void Remove(List items, List folders)
+ {
+ if ((items == null && items.Count == 0) && (folders == null && folders.Count == 0))
+ return;
+
+ RemoveInventoryObjectsPacket rem = new RemoveInventoryObjectsPacket();
+ rem.AgentData.AgentID = Client.Network.AgentID;
+ rem.AgentData.SessionID = Client.Network.SessionID;
+
+ if (items == null || items.Count == 0)
+ {
+ // To indicate that we want no items removed:
+ rem.ItemData = new RemoveInventoryObjectsPacket.ItemDataBlock[1];
+ rem.ItemData[0] = new RemoveInventoryObjectsPacket.ItemDataBlock();
+ rem.ItemData[0].ItemID = LLUUID.Zero;
+ }
+ else
+ {
+ rem.ItemData = new RemoveInventoryObjectsPacket.ItemDataBlock[items.Count];
+ for (int i = 0; i < items.Count; ++i)
+ {
+ rem.ItemData[i] = new RemoveInventoryObjectsPacket.ItemDataBlock();
+ rem.ItemData[i].ItemID = items[i];
+ // Update local copy:
+ if (Store.Contains(items[i]))
+ Store.RemoveNodeFor(Store[items[i]]);
+ }
+ }
+
+ if (folders == null || folders.Count == 0)
+ {
+ // To indicate we want no folders removed:
+ rem.FolderData = new RemoveInventoryObjectsPacket.FolderDataBlock[1];
+ rem.FolderData[0] = new RemoveInventoryObjectsPacket.FolderDataBlock();
+ rem.FolderData[0].FolderID = LLUUID.Zero;
+ }
+ else
+ {
+ rem.FolderData = new RemoveInventoryObjectsPacket.FolderDataBlock[folders.Count];
+ for (int i = 0; i < folders.Count; ++i)
+ {
+ rem.FolderData[i] = new RemoveInventoryObjectsPacket.FolderDataBlock();
+ rem.FolderData[i].FolderID = folders[i];
+ // Update local copy:
+ if (Store.Contains(folders[i]))
+ Store.RemoveNodeFor(Store[folders[i]]);
+ }
+ }
+
+ }
+
+ #region Callbacks
+
+ private void InventoryDescendentsHandler(Packet packet, Simulator simulator)
+ {
+ InventoryDescendentsPacket reply = (InventoryDescendentsPacket)packet;
+ InventoryFolder parentFolder = null;
+
+ if (Store.Contains(reply.AgentData.FolderID) &&
+ Store[reply.AgentData.FolderID] is InventoryFolder)
+ {
+ parentFolder = Store[reply.AgentData.FolderID] as InventoryFolder;
+ }
+ else
+ {
+ Client.Log("Don't have a reference to FolderID " + reply.AgentData.FolderID.ToStringHyphenated() +
+ " or it is not a folder", Helpers.LogLevel.Error);
+ return;
+ }
+
+ if (reply.AgentData.Version < parentFolder.Version)
+ {
+ Client.Log("Got an outdated InventoryDescendents packet for folder " + parentFolder.Name +
+ ", this version = " + reply.AgentData.Version + ", latest version = " + parentFolder.Version,
+ Helpers.LogLevel.Warning);
+ return;
+ }
+
+ if (reply.AgentData.Descendents > 0)
+ {
+ // InventoryDescendantsReply sends a null folder if the parent doesnt contain any folders.
+ if (reply.FolderData[0].FolderID != LLUUID.Zero)
+ {
+ // Iterate folders in this packet
+ for (int i = 0; i < reply.FolderData.Length; i++)
+ {
+ InventoryFolder folder = new InventoryFolder(reply.FolderData[i].FolderID);
+ folder.ParentUUID = reply.FolderData[i].ParentID;
+ folder.Name = Helpers.FieldToUTF8String(reply.FolderData[i].Name);
+ folder.PreferredType = (AssetType)reply.FolderData[i].Type;
+ folder.OwnerID = reply.AgentData.OwnerID;
+
+ Store[folder.UUID] = folder;
+ }
+ }
+
+ // InventoryDescendantsReply sends a null item if the parent doesnt contain any items.
+ if (reply.ItemData[0].ItemID != LLUUID.Zero)
+ {
+ // Iterate items in this packet
+ for (int i = 0; i < reply.ItemData.Length; i++)
+ {
+ if (reply.ItemData[i].ItemID != LLUUID.Zero)
+ {
+ InventoryItem item = new InventoryItem(reply.ItemData[i].ItemID);
+ item.ParentUUID = reply.ItemData[i].FolderID;
+ item.AssetType = (AssetType)reply.ItemData[i].Type;
+ item.AssetUUID = reply.ItemData[i].AssetID;
+ item.CreationDate = Helpers.UnixTimeToDateTime((uint)reply.ItemData[i].CreationDate);
+ item.Description = Helpers.FieldToUTF8String(reply.ItemData[i].Description);
+ item.Flags = reply.ItemData[i].Flags;
+ item.InventoryType = (InventoryType)reply.ItemData[i].InvType;
+ item.Name = Helpers.FieldToUTF8String(reply.ItemData[i].Name);
+ item.GroupID = reply.ItemData[i].GroupID;
+ item.GroupOwned = reply.ItemData[i].GroupOwned;
+ item.Permissions = new Permissions(
+ reply.ItemData[i].BaseMask,
+ reply.ItemData[i].EveryoneMask,
+ reply.ItemData[i].GroupMask,
+ reply.ItemData[i].NextOwnerMask,
+ reply.ItemData[i].OwnerMask);
+ item.SalePrice = reply.ItemData[i].SalePrice;
+ item.SaleType = (SaleType)reply.ItemData[i].SaleType;
+ item.OwnerID = reply.AgentData.OwnerID;
+
+ Store[item.UUID] = item;
+ }
+ }
+ }
+ }
+
+ parentFolder.Version = reply.AgentData.Version;
+ parentFolder.DescendentCount = reply.AgentData.Descendents;
+
+ if (OnInventoryFolderUpdated != null)
+ {
+ try { OnInventoryFolderUpdated(parentFolder.UUID); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+
+ // For FindObjects - only unblock if we've received all the descendants
+ if (folderRequests.ContainsKey(parentFolder.UUID) && parentFolder.DescendentCount == Store.GetContents(parentFolder.UUID).Count)
+ HandleDescendantsForFind(parentFolder.UUID);
+ }
+
+ ///
+ /// UpdateCreateInventoryItem packets are received when a new inventory item
+ /// is created. This may occur when an object that's rezzed in world is
+ /// taken into inventory, when an item is created using the CreateInventoryItem
+ /// packet, or when an object is purchased.
+ ///
+ private void UpdateCreateInventoryItemHandler(Packet packet, Simulator simulator)
+ {
+ UpdateCreateInventoryItemPacket reply = packet as UpdateCreateInventoryItemPacket;
+
+ foreach (UpdateCreateInventoryItemPacket.InventoryDataBlock dataBlock in reply.InventoryData)
+ {
+ if (dataBlock.InvType == (sbyte)InventoryType.Folder) {
+ Client.Log("Received InventoryFolder in an UpdateCreateInventoryItem packet.", Helpers.LogLevel.Error);
+ continue;
+ }
+ InventoryItem item = new InventoryItem(dataBlock.ItemID);
+
+ item.AssetType = (AssetType)dataBlock.Type;
+ item.AssetUUID = dataBlock.AssetID;
+ item.CreationDate = DateTime.FromBinary(dataBlock.CreationDate);
+ item.Description = Helpers.FieldToUTF8String(dataBlock.Description);
+ item.Flags = dataBlock.Flags;
+ item.GroupID = dataBlock.GroupID;
+ item.GroupOwned = dataBlock.GroupOwned;
+ item.InventoryType = (InventoryType)dataBlock.InvType;
+ item.Name = Helpers.FieldToUTF8String(dataBlock.Name);
+ item.OwnerID = dataBlock.OwnerID;
+ item.ParentUUID = dataBlock.FolderID;
+ item.Permissions = new Permissions(
+ dataBlock.BaseMask,
+ dataBlock.EveryoneMask,
+ dataBlock.GroupMask,
+ dataBlock.NextOwnerMask,
+ dataBlock.OwnerMask);
+ item.SalePrice = dataBlock.SalePrice;
+ item.SaleType = (SaleType)dataBlock.SaleType;
+
+ Store[item.UUID] = item;
+ }
+ }
+
+ private void BulkUpdateInventoryHandler(Packet packet, Simulator simulator)
+ {
+ BulkUpdateInventoryPacket update = packet as BulkUpdateInventoryPacket;
+
+ if (update.FolderData.Length > 0 && update.FolderData[0].FolderID != LLUUID.Zero)
+ {
+ foreach (BulkUpdateInventoryPacket.FolderDataBlock dataBlock in update.FolderData)
+ {
+
+ if (Store.Contains(dataBlock.FolderID))
+ Client.Log("Received BulkUpdate for unknown folder: " + dataBlock.FolderID, Helpers.LogLevel.Warning);
+
+ InventoryFolder folder = new InventoryFolder(dataBlock.FolderID);
+ folder.Name = Helpers.FieldToUTF8String(dataBlock.Name);
+ folder.OwnerID = update.AgentData.AgentID;
+ folder.ParentUUID = dataBlock.ParentID;
+ Store[folder.UUID] = folder;
+ }
+ }
+
+ if (update.ItemData.Length > 0 && update.ItemData[0].ItemID != LLUUID.Zero)
+ {
+ foreach (BulkUpdateInventoryPacket.ItemDataBlock dataBlock in update.ItemData)
+ {
+ if (!Store.Contains(dataBlock.ItemID))
+ Client.Log("Received BulkUpdate for unknown item: " + dataBlock.ItemID, Helpers.LogLevel.Warning);
+
+ InventoryItem item = new InventoryItem(dataBlock.ItemID);
+ item.AssetType = (AssetType)dataBlock.Type;
+ item.AssetUUID = dataBlock.AssetID; // FIXME: Should we set this here? Isnt it always zero?
+ item.CreationDate = DateTime.FromBinary(dataBlock.CreationDate);
+ item.Description = Helpers.FieldToUTF8String(dataBlock.Description);
+ item.Flags = dataBlock.Flags;
+ item.GroupID = dataBlock.GroupID;
+ item.GroupOwned = dataBlock.GroupOwned;
+ item.InventoryType = (InventoryType)dataBlock.InvType;
+ item.Name = Helpers.FieldToUTF8String(dataBlock.Name);
+ item.OwnerID = dataBlock.OwnerID;
+ item.ParentUUID = dataBlock.FolderID;
+ item.Permissions = new Permissions(
+ dataBlock.BaseMask,
+ dataBlock.EveryoneMask,
+ dataBlock.GroupMask,
+ dataBlock.NextOwnerMask,
+ dataBlock.OwnerMask);
+ item.SalePrice = dataBlock.SalePrice;
+ item.SaleType = (SaleType)dataBlock.SaleType;
+
+ Store[item.UUID] = item;
+ }
+ }
+ }
+
+ private void FetchInventoryReplyHandler(Packet packet, Simulator simulator)
+ {
+ FetchInventoryReplyPacket reply = packet as FetchInventoryReplyPacket;
+
+ foreach (FetchInventoryReplyPacket.InventoryDataBlock dataBlock in reply.InventoryData)
+ {
+ if (dataBlock.InvType == (sbyte)InventoryType.Folder)
+ {
+ Client.Log("Received FetchInventoryReply for inventory folder!", Helpers.LogLevel.Error);
+ continue;
+ }
+
+ InventoryItem item = new InventoryItem(dataBlock.ItemID);
+ item.AssetType = (AssetType)dataBlock.Type;
+ item.AssetUUID = dataBlock.AssetID;
+ item.CreationDate = DateTime.FromBinary(dataBlock.CreationDate);
+ item.Description = Helpers.FieldToUTF8String(dataBlock.Description);
+ item.Flags = dataBlock.Flags;
+ item.GroupID = dataBlock.GroupID;
+ item.GroupOwned = dataBlock.GroupOwned;
+ item.InventoryType = (InventoryType)dataBlock.InvType;
+ item.Name = Helpers.FieldToUTF8String(dataBlock.Name);
+ item.OwnerID = dataBlock.OwnerID;
+ item.ParentUUID = dataBlock.FolderID;
+ item.Permissions = new Permissions(
+ dataBlock.BaseMask,
+ dataBlock.EveryoneMask,
+ dataBlock.GroupMask,
+ dataBlock.NextOwnerMask,
+ dataBlock.OwnerMask);
+ item.SalePrice = dataBlock.SalePrice;
+ item.SaleType = (SaleType)dataBlock.SaleType;
+
+ Store[item.UUID] = item;
+ }
+ }
+ private void Self_OnInstantMessage(LLUUID fromAgentID, string fromAgentName, LLUUID toAgentID,
+ uint parentEstateID, LLUUID regionID, LLVector3 position, MainAvatar.InstantMessageDialog dialog,
+ bool groupIM, LLUUID imSessionID, DateTime timestamp, string message,
+ MainAvatar.InstantMessageOnline offline, byte[] binaryBucket, Simulator simulator)
+ {
+ // TODO: MainAvatar.InstantMessageDialog.GroupNotice can also be an inventory offer, should we
+ // handle it here?
+
+ if (OnInventoryObjectReceived != null)
+ {
+ AssetType type = AssetType.Unknown;
+ LLUUID objectID = LLUUID.Zero;
+ bool fromTask = false;
+
+ if (dialog == MainAvatar.InstantMessageDialog.InventoryOffered)
+ {
+ if (binaryBucket.Length == 17)
+ {
+ type = (AssetType)binaryBucket[0];
+ objectID = new LLUUID(binaryBucket, 1);
+ fromTask = false;
+ }
+ else
+ {
+ Client.Log("Malformed inventory offer from agent", Helpers.LogLevel.Warning);
+ return;
+ }
+ }
+ else if (dialog == MainAvatar.InstantMessageDialog.TaskInventoryOffered)
+ {
+ if (binaryBucket.Length == 1)
+ {
+ type = (AssetType)binaryBucket[0];
+ fromTask = true;
+ }
+ else
+ {
+ Client.Log("Malformed inventory offer from object", Helpers.LogLevel.Warning);
+ return;
+ }
+ }
+
+ // Find the folder where this is going to go
+ LLUUID destinationFolderID = FindFolderForType(type);
+
+ // Fire the callback
+ try
+ {
+ ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
+ im.AgentData.AgentID = Client.Network.AgentID;
+ im.AgentData.SessionID = Client.Network.SessionID;
+ im.MessageBlock.FromGroup = false;
+ im.MessageBlock.ToAgentID = fromAgentID;
+ im.MessageBlock.Offline = 0;
+ im.MessageBlock.ID = imSessionID;
+ im.MessageBlock.Timestamp = 0;
+ im.MessageBlock.FromAgentName = Helpers.StringToField(Client.ToString());
+ im.MessageBlock.Message = new byte[0];
+ im.MessageBlock.ParentEstateID = 0;
+ im.MessageBlock.RegionID = LLUUID.Zero;
+ im.MessageBlock.Position = Client.Self.Position;
+
+ if (OnInventoryObjectReceived(fromAgentID, fromAgentName, parentEstateID, regionID, position,
+ timestamp, type, objectID, fromTask))
+ {
+ // Accept the inventory offer
+ switch (dialog)
+ {
+ case MainAvatar.InstantMessageDialog.InventoryOffered:
+ im.MessageBlock.Dialog = (byte)MainAvatar.InstantMessageDialog.InventoryAccepted;
+ break;
+ case MainAvatar.InstantMessageDialog.TaskInventoryOffered:
+ im.MessageBlock.Dialog = (byte)MainAvatar.InstantMessageDialog.TaskInventoryOffered;
+ break;
+ case MainAvatar.InstantMessageDialog.GroupNotice:
+ im.MessageBlock.Dialog = (byte)MainAvatar.InstantMessageDialog.GroupNoticeInventoryAccepted;
+ break;
+ }
+
+ im.MessageBlock.BinaryBucket = destinationFolderID.GetBytes();
+ }
+ else
+ {
+ // Decline the inventory offer
+ switch (dialog)
+ {
+ case MainAvatar.InstantMessageDialog.InventoryOffered:
+ im.MessageBlock.Dialog = (byte)MainAvatar.InstantMessageDialog.InventoryDeclined;
+ break;
+ case MainAvatar.InstantMessageDialog.TaskInventoryOffered:
+ im.MessageBlock.Dialog = (byte)MainAvatar.InstantMessageDialog.TaskInventoryDeclined;
+ break;
+ case MainAvatar.InstantMessageDialog.GroupNotice:
+ im.MessageBlock.Dialog = (byte)MainAvatar.InstantMessageDialog.GroupNoticeInventoryDeclined;
+ break;
+ }
+
+ im.MessageBlock.BinaryBucket = new byte[0];
+ }
+
+ Client.Network.SendPacket(im, simulator);
+ }
+ catch (Exception e)
+ {
+ Client.Log(e.ToString(), Helpers.LogLevel.Error);
+ }
+ }
+ }
+
+ #endregion Callbacks
+ }
+
+
+ class FindResult : IAsyncResult
+ {
+ public List Result;
+
+ public bool Recurse
+ {
+ get { return recurse; }
+ }
+
+ public Regex Regex
+ {
+ get { return regex; }
+ }
+ public AsyncCallback Callback
+ {
+ get { return callback; }
+ }
+ public int FoldersWaiting;
+
+ private AsyncCallback callback;
+ private Regex regex;
+ private bool recurse;
+ private ManualResetEvent waitHandle;
+ private bool complete;
+ private bool sync;
+ private object asyncstate;
+
+ public FindResult(Regex regex, bool recurse, AsyncCallback callback)
+ {
+ this.waitHandle = new ManualResetEvent(false);
+ this.callback = callback;
+ this.recurse = recurse;
+ this.regex = regex;
+ this.Result = new List();
+ }
+
+ #region IAsyncResult Members
+
+ public object AsyncState
+ {
+ get { return asyncstate; }
+ set { asyncstate = value; }
+ }
+
+ public WaitHandle AsyncWaitHandle
+ {
+ get { return waitHandle; }
+ }
+
+ public bool CompletedSynchronously
+ {
+ get { return sync; }
+ set { sync = value; }
+ }
+
+ public bool IsCompleted
+ {
+ get { return complete; }
+ set
+ {
+ if (value)
+ {
+ waitHandle.Set();
+ if (callback != null)
+ {
+ callback.Invoke(this);
+ }
+ }
+ complete = value;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/libsecondlife/InventoryNode.cs b/libsecondlife/InventoryNode.cs
new file mode 100644
index 00000000..a9f9d7d5
--- /dev/null
+++ b/libsecondlife/InventoryNode.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace libsecondlife
+{
+ public class InventoryNode
+ {
+ private InventoryObject data;
+ private InventoryNode parent;
+ private InventoryNodeDictionary nodes;
+
+ ///
+ public InventoryObject Data
+ {
+ get { return data; }
+ set { data = value; }
+ }
+
+ ///
+ public InventoryNode Parent
+ {
+ get { return parent; }
+ set { parent = value; }
+ }
+
+ ///
+ public InventoryNodeDictionary Nodes
+ {
+ get
+ {
+ if (nodes == null)
+ nodes = new InventoryNodeDictionary(this);
+
+ return nodes;
+ }
+ set { nodes = value; }
+ }
+
+ ///
+ ///
+ ///
+ public InventoryNode()
+ {
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public InventoryNode(InventoryObject data)
+ {
+ this.data = data;
+ }
+
+ public InventoryNode(InventoryObject data, InventoryNode parent)
+ {
+ this.data = data;
+ this.parent = parent;
+
+ if (parent != null)
+ {
+ // Add this node to the collection of parent nodes
+ lock (parent.Nodes.SyncRoot) parent.Nodes.Add(data.UUID, this);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override string ToString()
+ {
+ if (this.Data == null) return "[Empty Node]";
+ return this.Data.ToString();
+ }
+ }
+}
diff --git a/libsecondlife/InventoryNodeDictionary.cs b/libsecondlife/InventoryNodeDictionary.cs
new file mode 100644
index 00000000..96608196
--- /dev/null
+++ b/libsecondlife/InventoryNodeDictionary.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+
+namespace libsecondlife
+{
+ public class InventoryNodeDictionary : DictionaryBase
+ {
+ protected InventoryNode parent;
+ protected object syncRoot = new object();
+
+ public InventoryNode Parent
+ {
+ get { return parent; }
+ set { parent = value; }
+ }
+
+ public object SyncRoot { get { return syncRoot; } }
+
+ public InventoryNodeDictionary(InventoryNode parentNode)
+ {
+ parent = parentNode;
+ }
+
+ public InventoryNode this[LLUUID key]
+ {
+ get { return (InventoryNode)this.Dictionary[key]; }
+ set
+ {
+ value.Parent = parent;
+ lock (syncRoot) this.Dictionary[key] = value;
+ }
+ }
+
+ public ICollection Keys { get { return this.Dictionary.Keys; } }
+ public ICollection Values { get { return this.Dictionary.Values; } }
+
+ public void Add(LLUUID key, InventoryNode value)
+ {
+ value.Parent = parent;
+ lock (syncRoot) this.Dictionary.Add(key, value);
+ }
+
+ public void Remove(LLUUID key)
+ {
+ lock (syncRoot) this.Dictionary.Remove(key);
+ }
+
+ public bool Contains(LLUUID key)
+ {
+ return this.Dictionary.Contains(key);
+ }
+ }
+}
diff --git a/libsecondlife/Login.cs b/libsecondlife/Login.cs
index 694eeb9f..7088f97e 100644
--- a/libsecondlife/Login.cs
+++ b/libsecondlife/Login.cs
@@ -56,6 +56,24 @@ namespace libsecondlife
public partial class NetworkManager
{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public delegate void LoginReplyCallback(bool loginSuccess, bool redirect, IPAddress simIP, int simPort,
+ uint regionX, uint regionY, string reason, string message);
+ ///
+ ///
+ ///
+ ///
+ ///
public delegate void LoginCallback(LoginStatus login, string message);
///
@@ -116,8 +134,12 @@ namespace libsecondlife
Success
}
+ /// Called when a reply is received from the login server, the
+ /// login sequence will block until this event returns
+ public event LoginReplyCallback OnLoginReply;
+
/// Called any time the login status changes, will eventually
- /// return Success or Failure
+ /// return LoginStatus.Success or LoginStatus.Failure
public event LoginCallback OnLogin;
/// Seed CAPS URL returned from the login server
@@ -147,7 +169,6 @@ namespace libsecondlife
public byte[] XMLRPC;
}
- private object LockObject = new object();
private LoginContext CurrentContext = null;
private ManualResetEvent LoginEvent = new ManualResetEvent(false);
private LoginStatus InternalStatusCode = LoginStatus.None;
@@ -258,22 +279,18 @@ namespace libsecondlife
BeginLogin(loginParams);
LoginEvent.WaitOne(loginParams.Timeout, false);
- lock (LockObject)
- {
- if (CurrentContext != null)
- {
- if (CurrentContext.Request != null)
- CurrentContext.Request.Abort();
- CurrentContext = null; // Will force any pending callbacks to bail out early
- InternalStatusCode = LoginStatus.Failed;
- InternalLoginMessage = "Timed out";
- return false;
- }
- if (InternalStatusCode != LoginStatus.Success)
- return false;
+ if (CurrentContext != null)
+ {
+ if (CurrentContext.Request != null)
+ CurrentContext.Request.Abort();
+ CurrentContext = null; // Will force any pending callbacks to bail out early
+ InternalStatusCode = LoginStatus.Failed;
+ InternalLoginMessage = "Timed out";
+ return false;
}
- return true;
+
+ return (InternalStatusCode == LoginStatus.Success);
}
private void BeginLogin()
@@ -349,18 +366,15 @@ namespace libsecondlife
public void BeginLogin(LoginParams loginParams)
{
- lock (LockObject)
- {
- if (CurrentContext != null)
- throw new Exception("Login already in progress");
+ if (CurrentContext != null)
+ throw new Exception("Login already in progress");
- LoginEvent.Reset();
+ LoginEvent.Reset();
- CurrentContext = new LoginContext();
- CurrentContext.Params = loginParams;
+ CurrentContext = new LoginContext();
+ CurrentContext.Params = loginParams;
- BeginLogin();
- }
+ BeginLogin();
}
///
@@ -400,10 +414,46 @@ namespace libsecondlife
private void LoginRequestCallback(IAsyncResult result)
{
LoginContext myContext = result.AsyncState as LoginContext;
- if (myContext == null)
- return;
+ if (myContext == null) return;
- lock (LockObject)
+ if (myContext != CurrentContext)
+ {
+ if (myContext.Request != null)
+ {
+ myContext.Request.Abort();
+ myContext.Request = null;
+ }
+ return;
+ }
+
+ try
+ {
+ byte[] bytes = myContext.XMLRPC;
+ Stream output = myContext.Request.EndGetRequestStream(result);
+
+ // Build the request
+ output.Write(bytes, 0, bytes.Length);
+ output.Close();
+
+ UpdateLoginStatus(LoginStatus.ReadingResponse, "Reading XMLRPC response...");
+ myContext.Request.BeginGetResponse(new AsyncCallback(LoginResponseCallback), myContext);
+ }
+ catch (WebException e)
+ {
+ UpdateLoginStatus(LoginStatus.Failed, "Error connecting to the login server: " + e.Message);
+ }
+ }
+
+ private void LoginResponseCallback(IAsyncResult result)
+ {
+ LoginContext myContext = result.AsyncState as LoginContext;
+ if (myContext == null) return;
+
+ HttpWebResponse response = null;
+ Stream xmlStream = null;
+ XmlReader reader = null;
+
+ try
{
if (myContext != CurrentContext)
{
@@ -415,390 +465,222 @@ namespace libsecondlife
return;
}
- try
+ response = (HttpWebResponse)myContext.Request.EndGetResponse(result);
+
+ xmlStream = response.GetResponseStream();
+
+ MemoryStream memStream = new MemoryStream();
+ BinaryReader streamReader = new BinaryReader(xmlStream);
+ BinaryWriter streamWriter = new BinaryWriter(memStream);
+
+ // Put the entire response in to a byte array
+ byte[] buffer;
+ while ((buffer = streamReader.ReadBytes(1024)) != null)
{
- byte[] bytes = myContext.XMLRPC;
- Stream output = myContext.Request.EndGetRequestStream(result);
-
- // Build the request
- output.Write(bytes, 0, bytes.Length);
- output.Close();
-
- UpdateLoginStatus(LoginStatus.ReadingResponse, "Reading XMLRPC response...");
- myContext.Request.BeginGetResponse(new AsyncCallback(LoginResponseCallback), myContext);
- //String r = new StreamReader(myContext.Request.GetResponse().GetResponseStream()).ReadToEnd();
+ if (buffer.Length == 0)
+ break;
+ streamWriter.Write(buffer);
}
- catch (WebException e)
+ streamWriter.Flush();
+ xmlStream.Close();
+
+ // Write the entire memory stream out to a byte array
+ buffer = memStream.ToArray();
+
+ // Reset the position in the stream to the beginning
+ memStream.Seek(0, SeekOrigin.Begin);
+
+ // The memory stream will become an XML stream shortly
+ xmlStream = memStream;
+
+ InternalRawLoginReply = Encoding.UTF8.GetString(buffer);
+
+ reader = XmlReader.Create(xmlStream);
+
+ // Parse the incoming xml
+ bool redirect = false;
+ string nextURL = String.Empty;
+ string nextMethod = String.Empty;
+ string name, value;
+ IPAddress simIP = IPAddress.Loopback;
+ ushort simPort = 0;
+ uint regionX = 0;
+ uint regionY = 0;
+ bool loginSuccess = false;
+ string reason = String.Empty;
+ string message = String.Empty;
+
+ reader.ReadStartElement("methodResponse");
+
+ if (!reader.IsStartElement("fault"))
{
- UpdateLoginStatus(LoginStatus.Failed, "Error connecting to the login server: " + e.Message);
- }
- }
- }
+ #region XML Parsing
- private void LoginResponseCallback(IAsyncResult result)
- {
- LoginContext myContext = result.AsyncState as LoginContext;
- if (myContext == null)
- return;
+ reader.ReadStartElement("params");
+ reader.ReadStartElement("param");
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("struct");
- lock (LockObject)
- {
- HttpWebResponse response = null;
- Stream xmlStream = null;
- XmlTextReader reader = null;
-
- try
- {
- if (myContext != CurrentContext)
+ while (reader.IsStartElement("member"))
{
- if (myContext.Request != null)
+ reader.ReadStartElement("member");
+ name = reader.ReadElementString("name");
+
+ switch (name)
{
- myContext.Request.Abort();
- myContext.Request = null;
- }
- return;
- }
+ case "login":
+ value = ReadStringValue(reader);
- response = (HttpWebResponse)myContext.Request.EndGetResponse(result);
+ if (value == "indeterminate")
+ redirect = true;
+ else if (value == "true")
+ loginSuccess = true;
- xmlStream = response.GetResponseStream();
+ break;
+ case "reason":
+ reason = ReadStringValue(reader);
+ InternalErrorKey = reason;
+ break;
+ case "agent_id":
+ LLUUID.TryParse(ReadStringValue(reader), out Client.Network.AgentID);
+ Client.Self.ID = Client.Network.AgentID;
+ break;
+ case "session_id":
+ LLUUID.TryParse(ReadStringValue(reader), out Client.Network.SessionID);
+ break;
+ case "secure_session_id":
+ LLUUID.TryParse(ReadStringValue(reader), out Client.Network.SecureSessionID);
+ break;
+ case "circuit_code":
+ Client.Network.CircuitCode = (uint)ReadIntegerValue(reader);
+ break;
+ case "first_name":
+ Client.Self.FirstName = ReadStringValue(reader).Trim(new char[] { '"' });
+ break;
+ case "last_name":
+ Client.Self.LastName = ReadStringValue(reader).Trim(new char[] { '"' });
+ break;
+ case "start_location":
+ Client.Self.StartLocation = ReadStringValue(reader);
+ break;
+ case "look_at":
+ ArrayList look_at = (ArrayList)LLSD.ParseTerseLLSD(ReadStringValue(reader));
+ Client.Self.LookAt = new LLVector3(
+ (float)(double)look_at[0],
+ (float)(double)look_at[1],
+ (float)(double)look_at[2]);
+ break;
+ case "home":
+ Hashtable home = (Hashtable)LLSD.ParseTerseLLSD(ReadStringValue(reader));
- MemoryStream memStream = new MemoryStream();
- BinaryReader streamReader = new BinaryReader(xmlStream);
- BinaryWriter streamWriter = new BinaryWriter(memStream);
+ ArrayList array = (ArrayList)home["position"];
+ Client.Self.HomePosition = new LLVector3(
+ (float)(double)array[0],
+ (float)(double)array[1],
+ (float)(double)array[2]);
- // Put the entire response in to a byte array
- byte[] buffer;
- while ((buffer = streamReader.ReadBytes(1024)) != null)
- {
- if (buffer.Length == 0)
- break;
- streamWriter.Write(buffer);
- }
- streamWriter.Flush();
- xmlStream.Close();
+ array = (ArrayList)home["look_at"];
+ Client.Self.HomeLookAt = new LLVector3(
+ (float)(double)array[0],
+ (float)(double)array[1],
+ (float)(double)array[2]);
+ break;
+ case "agent_access":
+ Client.Self.AgentAccess = ReadStringValue(reader);
+ break;
+ case "message":
+ message = ReadStringValue(reader);
+ break;
+ case "region_x":
+ regionX = (uint)ReadIntegerValue(reader);
+ break;
+ case "region_y":
+ regionY = (uint)ReadIntegerValue(reader);
+ break;
+ case "sim_port":
+ simPort = (ushort)ReadIntegerValue(reader);
+ break;
+ case "sim_ip":
+ IPAddress.TryParse(ReadStringValue(reader), out simIP);
+ break;
+ case "seconds_since_epoch":
+ uint timestamp = (uint)ReadIntegerValue(reader);
+ DateTime time = Helpers.UnixTimeToDateTime(timestamp);
+ // FIXME: ???
+ break;
+ case "seed_capability":
+ LoginSeedCapability = ReadStringValue(reader);
+ break;
+ case "inventory-root":
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("struct");
- // Write the entire memory stream out to a byte array
- buffer = memStream.ToArray();
+ ReadStringMember(reader, out name, out value);
+ if (LLUUID.TryParse(value, out Client.Self.InventoryRootFolderUUID))
+ Client.Inventory.InitializeRootNode(Client.Self.InventoryRootFolderUUID);
+ else
+ Client.Log("Failed to parse the inventory-root UUID, inventory system will not " +
+ "be properly initialized", Helpers.LogLevel.Error);
- // Reset the position in the stream to the beginning
- memStream.Seek(0, SeekOrigin.Begin);
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ case "inventory-lib-root":
+ reader.ReadStartElement("value");
- // The memory stream will become an XML stream shortly
- xmlStream = memStream;
+ // Fix this later
+ reader.Skip();
- InternalRawLoginReply = Encoding.UTF8.GetString(buffer);
+ //reader.ReadStartElement("array");
+ //reader.ReadStartElement("data");
+ //reader.ReadStartElement("value");
+ //reader.ReadStartElement("struct");
- //reader = XmlReader.Create(xmlStream);
- reader = new XmlTextReader(xmlStream);
+ //ReadStringMember(reader, out name, out value);
+ // FIXME:
+ //LLUUID.TryParse(value, out Client.Self.InventoryLibRootFolderUUID);
- // Parse the incoming xml
- bool redirect = false;
- string nextURL = String.Empty;
- string nextMethod = String.Empty;
- string name, value;
- IPAddress simIP = IPAddress.Loopback;
- ushort simPort = 0;
- bool loginSuccess = false;
- string reason = String.Empty;
- string message = String.Empty;
+ //reader.ReadEndElement();
+ //reader.ReadEndElement();
+ //reader.ReadEndElement();
+ //reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ case "inventory-lib-owner":
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("struct");
- reader.ReadStartElement("methodResponse");
+ ReadStringMember(reader, out name, out value);
+ // FIXME:
+ //LLUUID.TryParse(value, out Client.Self.InventoryLibOwnerUUID);
- if (!reader.IsStartElement("fault"))
- {
- #region XML Parsing
-
- reader.ReadStartElement("params");
- reader.ReadStartElement("param");
- reader.ReadStartElement("value");
- reader.ReadStartElement("struct");
-
- while (reader.IsStartElement("member"))
- {
- reader.ReadStartElement("member");
- name = reader.ReadElementString("name");
-
- switch (name)
- {
- case "login":
- value = ReadStringValue(reader);
-
- if (value == "indeterminate")
- redirect = true;
- else if (value == "true")
- loginSuccess = true;
-
- break;
- case "reason":
- reason = ReadStringValue(reader);
- InternalErrorKey = reason;
- break;
- case "agent_id":
- LLUUID.TryParse(ReadStringValue(reader), out Client.Network.AgentID);
- Client.Self.ID = Client.Network.AgentID;
- break;
- case "session_id":
- LLUUID.TryParse(ReadStringValue(reader), out Client.Network.SessionID);
- break;
- case "secure_session_id":
- LLUUID.TryParse(ReadStringValue(reader), out Client.Network.SecureSessionID);
- break;
- case "circuit_code":
- Client.Network.CircuitCode = (uint)ReadIntegerValue(reader);
- break;
- case "first_name":
- Client.Self.FirstName = ReadStringValue(reader).Trim(new char[] { '"' });
- break;
- case "last_name":
- Client.Self.LastName = ReadStringValue(reader).Trim(new char[] { '"' });
- break;
- case "start_location":
- Client.Self.StartLocation = ReadStringValue(reader);
- break;
- case "look_at":
- ArrayList look_at = (ArrayList)LLSD.ParseTerseLLSD(ReadStringValue(reader));
- Client.Self.LookAt = new LLVector3(
- (float)(double)look_at[0],
- (float)(double)look_at[1],
- (float)(double)look_at[2]);
- break;
- case "home":
- Hashtable home = (Hashtable)LLSD.ParseTerseLLSD(ReadStringValue(reader));
-
- ArrayList array = (ArrayList)home["position"];
- Client.Self.HomePosition = new LLVector3(
- (float)(double)array[0],
- (float)(double)array[1],
- (float)(double)array[2]);
-
- array = (ArrayList)home["look_at"];
- Client.Self.HomeLookAt = new LLVector3(
- (float)(double)array[0],
- (float)(double)array[1],
- (float)(double)array[2]);
- break;
- case "agent_access":
- Client.Self.AgentAccess = ReadStringValue(reader);
- break;
- case "message":
- message = ReadStringValue(reader);
- break;
- case "region_x":
- //FIXME:
- int regionX = ReadIntegerValue(reader);
- break;
- case "region_y":
- // FIXME:
- int regionY = ReadIntegerValue(reader);
- break;
- case "sim_port":
- simPort = (ushort)ReadIntegerValue(reader);
- break;
- case "sim_ip":
- IPAddress.TryParse(ReadStringValue(reader), out simIP);
- break;
- case "seconds_since_epoch":
- uint timestamp = (uint)ReadIntegerValue(reader);
- DateTime time = Helpers.UnixTimeToDateTime(timestamp);
- // FIXME: ???
- break;
- case "seed_capability":
- LoginSeedCapability = ReadStringValue(reader);
- break;
- case "inventory-root":
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
- reader.ReadStartElement("value");
- reader.ReadStartElement("struct");
-
- ReadStringMember(reader, out name, out value);
- LLUUID.TryParse(value, out Client.Self.InventoryRootFolderUUID);
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- case "inventory-lib-root":
- reader.ReadStartElement("value");
-
- // Fix this later
- reader.Skip();
-
- //reader.ReadStartElement("array");
- //reader.ReadStartElement("data");
- //reader.ReadStartElement("value");
- //reader.ReadStartElement("struct");
-
- //ReadStringMember(reader, out name, out value);
- // FIXME:
- //LLUUID.TryParse(value, out Client.Self.InventoryLibRootFolderUUID);
-
- //reader.ReadEndElement();
- //reader.ReadEndElement();
- //reader.ReadEndElement();
- //reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- case "inventory-lib-owner":
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
- reader.ReadStartElement("value");
- reader.ReadStartElement("struct");
-
- ReadStringMember(reader, out name, out value);
- // FIXME:
- //LLUUID.TryParse(value, out Client.Self.InventoryLibOwnerUUID);
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- case "inventory-skeleton":
- {
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
-
- int typeDefault, version;
- string invName;
- LLUUID folderID, parentID;
-
- while (ReadInventoryMember(reader, out typeDefault, out version, out invName,
- out folderID, out parentID))
- {
- // FIXME:
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- }
- case "inventory-skel-lib":
- {
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
-
- int typeDefault, version;
- string invName;
- LLUUID folderID, parentID;
-
- while (ReadInventoryMember(reader, out typeDefault, out version, out invName,
- out folderID, out parentID))
- {
- // FIXME:
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- }
- case "gestures":
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ case "inventory-skeleton":
+ {
reader.ReadStartElement("value");
reader.ReadStartElement("array");
reader.ReadStartElement("data");
- while (reader.IsStartElement("value"))
- {
- reader.ReadStartElement("value");
- reader.ReadStartElement("struct");
+ int typeDefault, version;
+ string invName;
+ LLUUID folderID, parentID;
- while (ReadStringMember(reader, out name, out value))
- {
- switch (name)
- {
- case "asset_id":
- // FIXME:
- break;
- case "item_id":
- // FIXME:
- break;
- default:
- Client.Log("Unhandled element in login reply (gestures)",
- Helpers.LogLevel.Error);
- reader.Skip();
- break;
- }
-
- // FIXME:
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- case "event_categories":
- {
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
-
- int categoryID;
- string categoryName;
-
- while (ReadCategoryMember(Client, reader, out categoryID, out categoryName))
- {
- // FIXME:
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- }
- case "classified_categories":
- {
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
-
- int categoryID;
- string categoryName;
-
- while (ReadCategoryMember(Client, reader, out categoryID, out categoryName))
- {
- // FIXME:
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- }
- case "event_notifications":
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
-
- // FIXME:
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- case "buddy-list":
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
-
- int buddyRightsGiven, buddyRightsHas;
- LLUUID buddyID;
-
- while (ReadBuddyMember(Client, reader, out buddyRightsGiven, out buddyRightsHas,
- out buddyID))
+ while (ReadInventoryMember(reader, out typeDefault, out version, out invName,
+ out folderID, out parentID))
{
// FIXME:
}
@@ -807,211 +689,355 @@ namespace libsecondlife
reader.ReadEndElement();
reader.ReadEndElement();
break;
- case "ui-config":
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
- reader.ReadStartElement("value");
- reader.ReadStartElement("struct");
-
- while (ReadStringMember(reader, out name, out value))
- {
- switch (name)
- {
- case "allow_first_life":
- // FIXME:
- break;
- default:
- Client.Log("Unhandled element in login reply (ui-config)",
- Helpers.LogLevel.Error);
- reader.Skip();
- break;
- }
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- case "login-flags":
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
- reader.ReadStartElement("value");
- reader.ReadStartElement("struct");
-
- while (ReadStringMember(reader, out name, out value))
- {
- switch (name)
- {
- case "ever_logged_in":
- // FIXME:
- break;
- case "daylight_savings":
- // FIXME:
- break;
- case "stipend_since_login":
- // FIXME:
- break;
- case "gendered":
- // FIXME:
- break;
- default:
- Client.Log("Unhandled element in login reply (login-flags)",
- Helpers.LogLevel.Error);
- reader.Skip();
- break;
- }
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- case "global-textures":
- reader.ReadStartElement("value");
- reader.ReadStartElement("array");
- reader.ReadStartElement("data");
- reader.ReadStartElement("value");
- reader.ReadStartElement("struct");
-
- while (ReadStringMember(reader, out name, out value))
- {
- switch (name)
- {
- case "cloud_texture_id":
- // FIXME:
- break;
- case "sun_texture_id":
- // FIXME:
- break;
- case "moon_texture_id":
- // FIXME:
- break;
- default:
- Client.Log("Unhandled element in login reply (global-textures)",
- Helpers.LogLevel.Error);
- reader.Skip();
- break;
- }
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- break;
- case "next_options":
- // FIXME: Parse the next_options and only use those for the next_url
- reader.Skip();
- break;
- case "next_duration":
- // FIXME: Use this value as the timeout for the next request
- reader.Skip();
- break;
- case "next_url":
- nextURL = ReadStringValue(reader);
- break;
- case "next_method":
- nextMethod = ReadStringValue(reader);
- break;
- default:
- Client.Log("Unhandled element in login reply", Helpers.LogLevel.Error);
- reader.Skip();
- break;
- }
-
- reader.ReadEndElement();
- }
-
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
- reader.ReadEndElement();
-
- #endregion XML Parsing
-
- if (redirect)
- {
- UpdateLoginStatus(LoginStatus.Redirecting, "Redirecting login...");
-
- // Handle indeterminate logins
- CurrentContext = new LoginContext();
- CurrentContext.Params = myContext.Params;
- CurrentContext.Params.URI = nextURL;
- CurrentContext.Params.MethodName = nextMethod;
- BeginLogin();
- }
- else if (loginSuccess)
- {
- UpdateLoginStatus(LoginStatus.ConnectingToSim, "Connecting to simulator...");
-
- // Connect to the sim given in the login reply
- if (Connect(simIP, simPort, true, LoginSeedCapability) != null)
- {
- // Request the economy data right after login
- SendPacket(new EconomyDataRequestPacket());
-
- // Update the login message with the MOTD returned from the server
- UpdateLoginStatus(LoginStatus.Success, message);
-
- // Fire an event for connecting to the grid
- if (OnConnected != null)
- {
- try { OnConnected(this.Client); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
- }
- else
+ case "inventory-skel-lib":
+ {
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+
+ int typeDefault, version;
+ string invName;
+ LLUUID folderID, parentID;
+
+ while (ReadInventoryMember(reader, out typeDefault, out version, out invName,
+ out folderID, out parentID))
+ {
+ // FIXME:
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ }
+ case "gestures":
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+
+ while (reader.IsStartElement("value"))
+ {
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("struct");
+
+ while (ReadStringMember(reader, out name, out value))
+ {
+ switch (name)
+ {
+ case "asset_id":
+ // FIXME:
+ break;
+ case "item_id":
+ // FIXME:
+ break;
+ default:
+ Client.Log("Unhandled element in login reply (gestures)",
+ Helpers.LogLevel.Error);
+ reader.Skip();
+ break;
+ }
+
+ // FIXME:
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ case "event_categories":
+ {
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+
+ int categoryID;
+ string categoryName;
+
+ while (ReadCategoryMember(Client, reader, out categoryID, out categoryName))
+ {
+ // FIXME:
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ }
+ case "classified_categories":
+ {
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+
+ int categoryID;
+ string categoryName;
+
+ while (ReadCategoryMember(Client, reader, out categoryID, out categoryName))
+ {
+ // FIXME:
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ }
+ case "event_notifications":
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+
+ // FIXME:
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ case "buddy-list":
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+
+ int buddyRightsGiven, buddyRightsHas;
+ LLUUID buddyID;
+
+ while (ReadBuddyMember(Client, reader, out buddyRightsGiven, out buddyRightsHas,
+ out buddyID))
+ {
+ // FIXME:
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ case "ui-config":
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("struct");
+
+ while (ReadStringMember(reader, out name, out value))
+ {
+ switch (name)
+ {
+ case "allow_first_life":
+ // FIXME:
+ break;
+ default:
+ Client.Log("Unhandled element in login reply (ui-config)",
+ Helpers.LogLevel.Error);
+ reader.Skip();
+ break;
+ }
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ case "login-flags":
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("struct");
+
+ while (ReadStringMember(reader, out name, out value))
+ {
+ switch (name)
+ {
+ case "ever_logged_in":
+ // FIXME:
+ break;
+ case "daylight_savings":
+ // FIXME:
+ break;
+ case "stipend_since_login":
+ // FIXME:
+ break;
+ case "gendered":
+ // FIXME:
+ break;
+ default:
+ Client.Log("Unhandled element in login reply (login-flags)",
+ Helpers.LogLevel.Error);
+ reader.Skip();
+ break;
+ }
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ case "global-textures":
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("array");
+ reader.ReadStartElement("data");
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("struct");
+
+ while (ReadStringMember(reader, out name, out value))
+ {
+ switch (name)
+ {
+ case "cloud_texture_id":
+ // FIXME:
+ break;
+ case "sun_texture_id":
+ // FIXME:
+ break;
+ case "moon_texture_id":
+ // FIXME:
+ break;
+ default:
+ Client.Log("Unhandled element in login reply (global-textures)",
+ Helpers.LogLevel.Error);
+ reader.Skip();
+ break;
+ }
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ break;
+ case "next_options":
+ // FIXME: Parse the next_options and only use those for the next_url
+ reader.Skip();
+ break;
+ case "next_duration":
+ // FIXME: Use this value as the timeout for the next request
+ reader.Skip();
+ break;
+ case "next_url":
+ nextURL = ReadStringValue(reader);
+ break;
+ case "next_method":
+ nextMethod = ReadStringValue(reader);
+ break;
+ case "initial-outfit":
+ // FIXME:
+ reader.Skip();
+ break;
+ default:
+ Client.Log("Unhandled element in login reply (" + name + ")", Helpers.LogLevel.Error);
+ reader.Skip();
+ break;
+ }
+
+ reader.ReadEndElement();
+ }
+
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+ reader.ReadEndElement();
+
+ #endregion XML Parsing
+
+ if (OnLoginReply != null)
+ {
+ try
+ {
+ OnLoginReply(loginSuccess, redirect, simIP, simPort, regionX, regionY, reason, message);
+ }
+ catch (Exception ex)
+ {
+ Client.Log(ex.ToString(), Helpers.LogLevel.Error);
+ }
+ }
+
+ if (redirect)
+ {
+ UpdateLoginStatus(LoginStatus.Redirecting, "Redirecting login...");
+
+ // Handle indeterminate logins
+ CurrentContext = new LoginContext();
+ CurrentContext.Params = myContext.Params;
+ CurrentContext.Params.URI = nextURL;
+ CurrentContext.Params.MethodName = nextMethod;
+ BeginLogin();
+ }
+ else if (loginSuccess)
+ {
+ UpdateLoginStatus(LoginStatus.ConnectingToSim, "Connecting to simulator...");
+
+ ulong handle = Helpers.UIntsToLong(regionX, regionY);
+
+ // Connect to the sim given in the login reply
+ if (Connect(simIP, simPort, handle, true, LoginSeedCapability) != null)
+ {
+ // Request the economy data right after login
+ SendPacket(new EconomyDataRequestPacket());
+
+ // Update the login message with the MOTD returned from the server
+ UpdateLoginStatus(LoginStatus.Success, message);
+
+ // Fire an event for connecting to the grid
+ if (OnConnected != null)
{
- UpdateLoginStatus(LoginStatus.Failed, "Unable to connect to simulator");
+ try { OnConnected(this.Client); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
else
{
- // Make sure a usable error key is set
- if (!String.IsNullOrEmpty(reason))
- InternalErrorKey = reason;
- else
- InternalErrorKey = "unknown";
-
- UpdateLoginStatus(LoginStatus.Failed, message);
+ UpdateLoginStatus(LoginStatus.Failed, "Unable to connect to simulator");
}
}
else
{
- reader.ReadStartElement("fault");
- reader.ReadStartElement("value");
- reader.ReadStartElement("struct");
+ // Make sure a usable error key is set
+ if (!String.IsNullOrEmpty(reason))
+ InternalErrorKey = reason;
+ else
+ InternalErrorKey = "unknown";
- ReadStringMember(reader, out name, out value);
-
- UpdateLoginStatus(LoginStatus.Failed, value);
+ UpdateLoginStatus(LoginStatus.Failed, message);
}
+ }
+ else
+ {
+ reader.ReadStartElement("fault");
+ reader.ReadStartElement("value");
+ reader.ReadStartElement("struct");
+ ReadStringMember(reader, out name, out value);
+
+ UpdateLoginStatus(LoginStatus.Failed, value);
}
- catch (WebException e)
- {
- UpdateLoginStatus(LoginStatus.Failed, "Error reading response: " + e.Message);
- }
- catch (XmlException e)
- {
- UpdateLoginStatus(LoginStatus.Failed, "Error parsing reply XML: " + e.Message + Environment.NewLine + e.StackTrace);
- }
- finally
- {
- if (reader != null)
- reader.Close();
- if (xmlStream != null)
- xmlStream.Close();
- if (response != null)
- response.Close();
- }
+
+ }
+ catch (WebException e)
+ {
+ UpdateLoginStatus(LoginStatus.Failed, "Error reading response: " + e.Message);
+ }
+ catch (XmlException e)
+ {
+ UpdateLoginStatus(LoginStatus.Failed, "Error parsing reply XML: " + e.Message + Environment.NewLine + e.StackTrace);
+ }
+ finally
+ {
+ if (reader != null)
+ reader.Close();
+ if (xmlStream != null)
+ xmlStream.Close();
+ if (response != null)
+ response.Close();
}
}
diff --git a/libsecondlife/MainAvatar.cs b/libsecondlife/MainAvatar.cs
index 4298f2f2..39ccdd51 100644
--- a/libsecondlife/MainAvatar.cs
+++ b/libsecondlife/MainAvatar.cs
@@ -570,11 +570,12 @@ namespace libsecondlife
/// Text of message
/// Enum of whether this message is held for
/// offline avatars
- ///
+ /// Context specific packed data
+ /// Simulator where this IM was received from
public delegate void InstantMessageCallback(LLUUID fromAgentID, string fromAgentName,
LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position,
InstantMessageDialog dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message,
- InstantMessageOnline offline, byte[] binaryBucket);
+ InstantMessageOnline offline, byte[] binaryBucket, Simulator simulator);
///
/// Triggered for any status updates of a teleport (progress, failed, succeeded)
@@ -792,8 +793,9 @@ namespace libsecondlife
// Health callback
Client.Network.RegisterCallback(PacketType.HealthMessage, new NetworkManager.PacketCallback(HealthHandler));
- // Money callback
- Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, new NetworkManager.PacketCallback(BalanceHandler));
+ // Money callbacks
+ callback = new NetworkManager.PacketCallback(BalanceHandler);
+ Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, callback);
// Group callbacks
Client.Network.RegisterCallback(PacketType.JoinGroupReply, new NetworkManager.PacketCallback(JoinGroupHandler));
@@ -1802,6 +1804,7 @@ namespace libsecondlife
, Helpers.FieldToUTF8String(im.MessageBlock.Message)
, (InstantMessageOnline)im.MessageBlock.Offline
, im.MessageBlock.BinaryBucket
+ , simulator
);
}
}
@@ -1954,16 +1957,19 @@ namespace libsecondlife
/// Unused
private void BalanceHandler(Packet packet, Simulator simulator)
{
- MoneyBalanceReplyPacket mbrp = (MoneyBalanceReplyPacket)packet;
- balance = mbrp.MoneyData.MoneyBalance;
-
- if (OnMoneyBalanceReplyReceived != null)
+ if (packet.Type == PacketType.MoneyBalanceReply)
{
- try { OnMoneyBalanceReplyReceived(mbrp.MoneyData.TransactionID,
- mbrp.MoneyData.TransactionSuccess, mbrp.MoneyData.MoneyBalance,
- mbrp.MoneyData.SquareMetersCredit, mbrp.MoneyData.SquareMetersCommitted,
- Helpers.FieldToUTF8String(mbrp.MoneyData.Description)); }
- catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ MoneyBalanceReplyPacket mbrp = (MoneyBalanceReplyPacket)packet;
+ balance = mbrp.MoneyData.MoneyBalance;
+
+ if (OnMoneyBalanceReplyReceived != null)
+ {
+ try { OnMoneyBalanceReplyReceived(mbrp.MoneyData.TransactionID,
+ mbrp.MoneyData.TransactionSuccess, mbrp.MoneyData.MoneyBalance,
+ mbrp.MoneyData.SquareMetersCredit, mbrp.MoneyData.SquareMetersCommitted,
+ Helpers.FieldToUTF8String(mbrp.MoneyData.Description)); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
}
if (OnBalanceUpdated != null)
@@ -2012,7 +2018,7 @@ namespace libsecondlife
{
Client.Log("Got EstablishAgentCommunication for sim "
+ ipAndPort + ", seed cap " + (string)body["seed-capability"], Helpers.LogLevel.Info);
- sim.setSeedCaps((string)body["seed-capability"]);
+ sim.SetSeedCaps((string)body["seed-capability"]);
}
}
else
@@ -2074,8 +2080,8 @@ namespace libsecondlife
Client.DebugLog("TeleportFinish received from " + simulator.ToString() + ", Flags: " + flags.ToString());
// Connect to the new sim
- Simulator newSimulator = Client.Network.Connect(new IPAddress(finish.Info.SimIP),
- finish.Info.SimPort, true, seedcaps);
+ Simulator newSimulator = Client.Network.Connect(new IPAddress(finish.Info.SimIP),
+ finish.Info.SimPort, finish.Info.RegionHandle, true, seedcaps);
if (newSimulator != null)
{
@@ -2092,12 +2098,8 @@ namespace libsecondlife
teleportMessage = "Failed to connect to the new sim after a teleport";
TeleportStat = TeleportStatus.Failed;
- // Attempt to reconnect to the previous simulator
- // TODO: This hasn't been tested at all
- Client.Network.Connect(simulator.IPEndPoint.Address, (ushort)simulator.IPEndPoint.Port,
- true, simulator.SimCaps.Seedcaps);
-
- Client.Log(teleportMessage, Helpers.LogLevel.Warning);
+ // We're going to get disconnected now
+ Client.Log(teleportMessage, Helpers.LogLevel.Error);
}
}
else if (packet.Type == PacketType.TeleportCancel)
diff --git a/libsecondlife/ManagedThreadPool.cs b/libsecondlife/ManagedThreadPool.cs
deleted file mode 100644
index a549c4a5..00000000
--- a/libsecondlife/ManagedThreadPool.cs
+++ /dev/null
@@ -1,298 +0,0 @@
-// Stephen Toub
-// stoub@microsoft.com
-//
-// Very simple threadpool in C#.
-// 4/27/04
-
-using System;
-using System.Threading;
-using System.Collections;
-
-namespace Toub.Threading
-{
- /// Implementation of Dijkstra's PV Semaphore based on the Monitor class
- public class Semaphore
- {
- #region Member Variables
- /// The number of units alloted by this semaphore.
- private int _count;
- /// Lock for the semaphore.
- private object _semLock = new object();
- #endregion
-
- #region Construction
- /// Initialize the semaphore as a binary semaphore.
- public Semaphore()
- : this(1)
- {
- }
-
- /// Initialize the semaphore as a counting semaphore.
- /// Initial number of threads that can take out units from this semaphore.
- /// Throws if the count argument is less than 0.
- public Semaphore(int count)
- {
- if (count < 0) throw new ArgumentException("Semaphore must have a count of at least 0.", "count");
- _count = count;
- }
- #endregion
-
- #region Synchronization Operations
- /// V the semaphore (add 1 unit to it).
- public void AddOne() { V(); }
-
- /// P the semaphore (take out 1 unit from it).
- public void WaitOne() { P(); }
-
- /// P the semaphore (take out 1 unit from it).
- public void P()
- {
- // Lock so we can work in peace. This works because lock is actually
- // built around Monitor.
- lock (_semLock)
- {
- // Wait until a unit becomes available. We need to wait
- // in a loop in case someone else wakes up before us. This could
- // happen if the Monitor.Pulse statements were changed to Monitor.PulseAll
- // statements in order to introduce some randomness into the order
- // in which threads are woken.
- while (_count <= 0) Monitor.Wait(_semLock, Timeout.Infinite);
- _count--;
- }
- }
-
- /// V the semaphore (add 1 unit to it).
- public void V()
- {
- // Lock so we can work in peace. This works because lock is actually
- // built around Monitor.
- lock (_semLock)
- {
- // Release our hold on the unit of control. Then tell everyone
- // waiting on this object that there is a unit available.
- _count++;
- Monitor.Pulse(_semLock);
- }
- }
-
- /// Resets the semaphore to the specified count. Should be used cautiously.
- public void Reset(int count)
- {
- lock (_semLock) { _count = count; }
- }
- #endregion
- }
-
- /// Managed thread pool
- public class ManagedThreadPool
- {
- #region Constants
- /// Maximum number of threads the thread pool has at its disposal.
- private const int _maxWorkerThreads = 25;
- #endregion
-
- #region Member Variables
- /// Queue of all the callbacks waiting to be executed.
- private static Queue _waitingCallbacks;
- ///
- /// Used to signal that a worker thread is needed for processing. Note that multiple
- /// threads may be needed simultaneously and as such we use a semaphore instead of
- /// an auto reset event.
- ///
- private static Semaphore _workerThreadNeeded;
- /// List of all worker threads at the disposal of the thread pool.
- private static ArrayList _workerThreads;
- /// Number of threads currently active.
- private static int _inUseThreads;
- /// Lockable object for the pool.
- private static object _poolLock = new object();
- #endregion
-
- #region Construction and Finalization
- /// Initialize the thread pool.
- static ManagedThreadPool() { Initialize(); }
-
- /// Initializes the thread pool.
- private static void Initialize()
- {
- // Create our thread stores; we handle synchronization ourself
- // as we may run into situtations where multiple operations need to be atomic.
- // We keep track of the threads we've created just for good measure; not actually
- // needed for any core functionality.
- _waitingCallbacks = new Queue();
- _workerThreads = new ArrayList();
- _inUseThreads = 0;
-
- // Create our "thread needed" event
- _workerThreadNeeded = new Semaphore(0);
-
- // Create all of the worker threads
- for (int i = 0; i < _maxWorkerThreads; i++)
- {
- // Create a new thread and add it to the list of threads.
- Thread newThread = new Thread(new ThreadStart(ProcessQueuedItems));
- _workerThreads.Add(newThread);
-
- // Configure the new thread and start it
- newThread.Name = "ManagedPoolThread #" + i.ToString();
- newThread.IsBackground = true;
- newThread.Start();
- }
- }
- #endregion
-
- #region Public Methods
- /// Queues a user work item to the thread pool.
- ///
- /// A WaitCallback representing the delegate to invoke when the thread in the
- /// thread pool picks up the work item.
- ///
- public static void QueueUserWorkItem(WaitCallback callback)
- {
- // Queue the delegate with no state
- QueueUserWorkItem(callback, null);
- }
-
- /// Queues a user work item to the thread pool.
- ///
- /// A WaitCallback representing the delegate to invoke when the thread in the
- /// thread pool picks up the work item.
- ///
- ///
- /// The object that is passed to the delegate when serviced from the thread pool.
- ///
- public static void QueueUserWorkItem(WaitCallback callback, object state)
- {
- // Create a waiting callback that contains the delegate and its state.
- // At it to the processing queue, and signal that data is waiting.
- WaitingCallback waiting = new WaitingCallback(callback, state);
- lock (_poolLock) { _waitingCallbacks.Enqueue(waiting); }
- _workerThreadNeeded.AddOne();
- }
-
- /// Empties the work queue of any queued work items. Resets all threads in the pool.
- public static void Reset()
- {
- lock (_poolLock)
- {
- // Cleanup any waiting callbacks
- try
- {
- // Try to dispose of all remaining state
- foreach (object obj in _waitingCallbacks)
- {
- WaitingCallback callback = (WaitingCallback)obj;
- if (callback.State is IDisposable) ((IDisposable)callback.State).Dispose();
- }
- }
- catch { }
-
- // Shutdown all existing threads
- try
- {
- foreach (Thread thread in _workerThreads)
- {
- if (thread != null) thread.Abort("reset");
- }
- }
- catch { }
-
- // Reinitialize the pool (create new threads, etc.)
- Initialize();
- }
- }
- #endregion
-
- #region Properties
- /// Gets the number of threads at the disposal of the thread pool.
- public static int MaxThreads { get { return _maxWorkerThreads; } }
- /// Gets the number of currently active threads in the thread pool.
- public static int ActiveThreads { get { return _inUseThreads; } }
- /// Gets the number of callback delegates currently waiting in the thread pool.
- public static int WaitingCallbacks { get { lock (_poolLock) { return _waitingCallbacks.Count; } } }
- #endregion
-
- #region Thread Processing
- /// Event raised when there is an exception on a threadpool thread.
- public static event UnhandledExceptionEventHandler UnhandledException;
-
- /// A thread worker function that processes items from the work queue.
- private static void ProcessQueuedItems()
- {
- // Process indefinitely
- while (true)
- {
- _workerThreadNeeded.WaitOne();
-
- // Get the next item in the queue. If there is nothing there, go to sleep
- // for a while until we're woken up when a callback is waiting.
- WaitingCallback callback = null;
-
- // Try to get the next callback available. We need to lock on the
- // queue in order to make our count check and retrieval atomic.
- lock (_poolLock)
- {
- if (_waitingCallbacks.Count > 0)
- {
- try { callback = (WaitingCallback)_waitingCallbacks.Dequeue(); }
- catch { } // make sure not to fail here
- }
- }
-
- if (callback != null)
- {
- // We now have a callback. Execute it. Make sure to accurately
- // record how many callbacks are currently executing.
- try
- {
- Interlocked.Increment(ref _inUseThreads);
- callback.Callback(callback.State);
- }
- catch (Exception exc)
- {
- try
- {
- UnhandledExceptionEventHandler handler = UnhandledException;
- if (handler != null) handler(typeof(ManagedThreadPool), new UnhandledExceptionEventArgs(exc, false));
- }
- catch { }
- }
- finally
- {
- Interlocked.Decrement(ref _inUseThreads);
- }
- }
- }
- }
- #endregion
-
- /// Used to hold a callback delegate and the state for that delegate.
- private class WaitingCallback
- {
- #region Member Variables
- /// Callback delegate for the callback.
- private WaitCallback _callback;
- /// State with which to call the callback delegate.
- private object _state;
- #endregion
-
- #region Construction
- /// Initialize the callback holding object.
- /// Callback delegate for the callback.
- /// State with which to call the callback delegate.
- public WaitingCallback(WaitCallback callback, object state)
- {
- _callback = callback;
- _state = state;
- }
- #endregion
-
- #region Properties
- /// Gets the callback delegate for the callback.
- public WaitCallback Callback { get { return _callback; } }
- /// Gets the state with which to call the callback delegate.
- public object State { get { return _state; } }
- #endregion
- }
- }
-}
diff --git a/libsecondlife/NetworkManager.cs b/libsecondlife/NetworkManager.cs
index 0741556b..460b80c9 100644
--- a/libsecondlife/NetworkManager.cs
+++ b/libsecondlife/NetworkManager.cs
@@ -101,6 +101,21 @@ namespace libsecondlife
///
public delegate void PacketCallback(Packet packet, Simulator simulator);
///
+ /// Assigned by the OnConnected event. Raised when login was a success
+ ///
+ /// Reference to the SecondLife class that called the event
+ public delegate void ConnectedCallback(object sender);
+ ///
+ /// Assigned by the OnLogoutReply callback. Raised upone receipt of a LogoutReply packet during logout process.
+ ///
+ ///
+ public delegate void LogoutCallback(List inventoryItems);
+ ///
+ /// Triggered when a new connection to a simulator is established
+ ///
+ /// The simulator that is being connected to
+ public delegate void SimConnectedCallback(Simulator simulator);
+ ///
/// Triggered when a simulator other than the simulator that is currently
/// being occupied disconnects for whatever reason
///
@@ -121,12 +136,6 @@ namespace libsecondlife
///
/// A reference to the old value of CurrentSim
public delegate void CurrentSimChangedCallback(Simulator PreviousSimulator);
- ///
- /// Assigned by the OnConnected event. Raised when login was a success
- ///
- /// Reference to the SecondLife class that called the event
- public delegate void ConnectedCallback(object sender);
-
///
/// Event raised when the client was able to connected successfully.
@@ -134,15 +143,14 @@ namespace libsecondlife
/// Uses the ConnectedCallback delegate.
public event ConnectedCallback OnConnected;
///
- /// Assigned by the OnLogoutReply callback. Raised upone receipt of a LogoutReply packet during logout process.
- ///
- ///
- public delegate void LogoutCallback(List inventoryItems);
- ///
/// Event raised when a logout is confirmed by the simulator
///
public event LogoutCallback OnLogoutReply;
///
+ /// Event raised when a connection to a simulator is established
+ ///
+ public event SimConnectedCallback OnSimConnected;
+ ///
/// An event for the connection to a simulator other than the currently
/// occupied one disconnecting
///
@@ -219,8 +227,8 @@ namespace libsecondlife
ServicePointManager.Expect100Continue = false;
// Catch exceptions from threads in the managed threadpool
- Toub.Threading.ManagedThreadPool.UnhandledException +=
- new UnhandledExceptionEventHandler(ManagedThreadPool_UnhandledException);
+ AppDomain.CurrentDomain.UnhandledException +=
+ new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
}
///
@@ -331,12 +339,14 @@ namespace libsecondlife
///
/// IP address to connect to
/// Port to connect to
+ /// Handle for this simulator, to identify its
+ /// location in the grid
/// Whether to set CurrentSim to this new
/// connection, use this if the avatar is moving in to this simulator
/// URL of the capabilities server to use for
/// this sim connection
/// A Simulator object on success, otherwise null
- public Simulator Connect(IPAddress ip, ushort port, bool setDefault, string seedcaps)
+ public Simulator Connect(IPAddress ip, ushort port, ulong handle, bool setDefault, string seedcaps)
{
IPEndPoint endPoint = new IPEndPoint(ip, (int)port);
Simulator simulator = FindSimulator(endPoint);
@@ -344,7 +354,7 @@ namespace libsecondlife
if (simulator == null)
{
// We're not tracking this sim, create a new Simulator object
- simulator = new Simulator(Client, endPoint);
+ simulator = new Simulator(Client, endPoint, handle);
// Immediately add this simulator to the list of current sims. It will be removed if the
// connection fails
@@ -371,10 +381,17 @@ namespace libsecondlife
// Start a timer that checks if we've been disconnected
DisconnectTimer.Start();
+ if (setDefault) SetCurrentSim(simulator, seedcaps);
+
+ // Fire the simulator connection callback if one is registered
+ if (OnSimConnected != null)
+ {
+ try { OnSimConnected(simulator); }
+ catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
+ }
+
// If enabled, send an AgentThrottle packet to the server to increase our bandwidth
if (Client.Settings.SEND_AGENT_THROTTLE) Client.Throttle.Set(simulator);
-
- if (setDefault) SetCurrentSim(simulator, seedcaps);
}
else
{
@@ -533,7 +550,7 @@ namespace libsecondlife
}
else
{
- // Keep the Inbox size within a certain capacity
+ // Keep the PacketArchive size within a certain capacity
while (simulator.PacketArchive.Count >= Settings.PACKET_ARCHIVE_SIZE)
{
simulator.PacketArchive.Dequeue(); simulator.PacketArchive.Dequeue();
@@ -574,32 +591,6 @@ namespace libsecondlife
#region FireCallbacks
- if (Callbacks.ContainsKey(packet.Type))
- {
- List callbackArray = Callbacks[packet.Type];
-
- // Fire any registered callbacks
- for (int i = 0; i < callbackArray.Count; i++)
- {
- if (callbackArray[i] != null)
- {
- bool sync = Client.Settings.SYNC_PACKETCALLBACKS;
- if (sync)
- {
- callbackArray[i](packet, simulator);
- }
- else
- {
- PacketCallbackWrapper wrapper;
- wrapper.Callback = callbackArray[i];
- wrapper.Packet = packet;
- wrapper.Simulator = simulator;
- Toub.Threading.ManagedThreadPool.QueueUserWorkItem(callback, wrapper);
- }
- }
- }
- }
-
if (Callbacks.ContainsKey(PacketType.Default))
{
List callbackArray = Callbacks[PacketType.Default];
@@ -620,7 +611,33 @@ namespace libsecondlife
wrapper.Callback = callbackArray[i];
wrapper.Packet = packet;
wrapper.Simulator = simulator;
- Toub.Threading.ManagedThreadPool.QueueUserWorkItem(callback, wrapper);
+ ThreadPool.QueueUserWorkItem(callback, wrapper);
+ }
+ }
+ }
+ }
+
+ if (Callbacks.ContainsKey(packet.Type))
+ {
+ List callbackArray = Callbacks[packet.Type];
+
+ // Fire any registered callbacks
+ for (int i = 0; i < callbackArray.Count; i++)
+ {
+ if (callbackArray[i] != null)
+ {
+ bool sync = Client.Settings.SYNC_PACKETCALLBACKS;
+ if (sync)
+ {
+ callbackArray[i](packet, simulator);
+ }
+ else
+ {
+ PacketCallbackWrapper wrapper;
+ wrapper.Callback = callbackArray[i];
+ wrapper.Packet = packet;
+ wrapper.Simulator = simulator;
+ ThreadPool.QueueUserWorkItem(callback, wrapper);
}
}
}
@@ -647,7 +664,7 @@ namespace libsecondlife
Simulator oldSim = CurrentSim;
lock (Simulators) CurrentSim = simulator; // CurrentSim is synchronized against Simulators
- simulator.setSeedCaps(seedcaps);
+ simulator.SetSeedCaps(seedcaps);
// If the current simulator changed fire the callback
if (OnCurrentSimChanged != null && simulator != oldSim)
@@ -663,7 +680,7 @@ namespace libsecondlife
///
private void FinalizeLogout()
{
- LogoutTimer.Stop();
+ if (LogoutTimer != null) LogoutTimer.Stop();
// Shutdown the network layer
Shutdown(DisconnectType.ClientInitiated);
@@ -672,7 +689,7 @@ namespace libsecondlife
{
try
{
- OnDisconnected(DisconnectType.ClientInitiated, "");
+ OnDisconnected(DisconnectType.ClientInitiated, String.Empty);
}
catch (Exception e)
{
@@ -753,7 +770,7 @@ namespace libsecondlife
return null;
}
- private void ManagedThreadPool_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// An exception occurred in a packet callback, log it
Client.Log(((Exception)e.ExceptionObject).ToString(), Helpers.LogLevel.Error);
@@ -1079,7 +1096,7 @@ namespace libsecondlife
if (FindSimulator(endPoint) != null) return;
IPAddress address = new IPAddress(p.SimulatorInfo.IP);
- if (Connect(address, p.SimulatorInfo.Port, false, LoginSeedCapability) == null)
+ if (Connect(address, p.SimulatorInfo.Port, p.SimulatorInfo.Handle, false, LoginSeedCapability) == null)
{
Client.Log("Unabled to connect to new sim " + address + ":" + p.SimulatorInfo.Port,
Helpers.LogLevel.Error);
diff --git a/libsecondlife/ObjectPoolBase.cs b/libsecondlife/ObjectPoolBase.cs
new file mode 100644
index 00000000..43944f9a
--- /dev/null
+++ b/libsecondlife/ObjectPoolBase.cs
@@ -0,0 +1,525 @@
+/*
+ * Copyright (c) 2007, 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 System.Collections.Generic;
+using System.Threading;
+
+namespace libsecondlife
+{
+ public sealed class WrappedObject : IDisposable where T : class
+ {
+ private T _instance;
+ internal readonly ObjectPoolSegment _owningSegment;
+ internal readonly ObjectPoolBase _owningObjectPool;
+ private bool _disposed = false;
+
+ internal WrappedObject(ObjectPoolBase owningPool, ObjectPoolSegment ownerSegment, T activeInstance)
+ {
+ _owningObjectPool = owningPool;
+ _owningSegment = ownerSegment;
+ _instance = activeInstance;
+ }
+
+ ~WrappedObject()
+ {
+ // If the AppDomain is being unloaded, or the CLR is
+ // shutting down, just exit gracefully
+ if (Environment.HasShutdownStarted)
+ return;
+
+ //Object Reserrection in Action!
+ GC.ReRegisterForFinalize(this);
+
+ // return this instance back to the owning queue.
+ _owningObjectPool.CheckIn(_owningSegment, _instance);
+ }
+
+ ///
+ /// Returns an instance of the class that has been checked out of the Object Pool.
+ ///
+ public T Instance
+ {
+ get
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("WrappedObject");
+ return _instance;
+ }
+ }
+
+ ///
+ /// Checkes the instance back into the object pool.
+ ///
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+ _owningObjectPool.CheckIn(_owningSegment, _instance);
+ GC.SuppressFinalize(this);
+ }
+ }
+
+ public abstract class ObjectPoolBase : IDisposable where T : class
+ {
+ private int _itemsPerSegment = 32;
+ private int _minimumSegmentCount = 1;
+
+ // A segment won't be eligible for cleanup unless it's at least this old...
+ private TimeSpan _minimumAgeToCleanup = new TimeSpan(0, 5, 0);
+
+ // ever increasing segment counter
+ private int _activeSegment = 0;
+
+ private bool _gc = true;
+
+ private volatile bool _disposed = false;
+
+ private Dictionary> _segments = new Dictionary>();
+ private object _syncRoot = new object();
+ private object _timerLock = new object();
+
+ // create a timer that starts in 5 minutes, and gets called every 5 minutes.
+ System.Threading.Timer _timer;
+ int _cleanupFrequency;
+
+ ///
+ /// Creates a new instance of the ObjectPoolBase class. Initialize MUST be called
+ /// after using this constructor.
+ ///
+ protected ObjectPoolBase()
+ {
+ }
+
+ ///
+ /// Creates a new instance of the ObjectPool Base class.
+ ///
+ /// The object pool is composed of segments, which
+ /// are allocated whenever the size of the pool is exceeded. The number of items
+ /// in a segment should be large enough that allocating a new segmeng is a rare
+ /// thing. For example, on a server that will have 10k people logged in at once,
+ /// the receive buffer object pool should have segment sizes of at least 1000
+ /// byte arrays per segment.
+ ///
+ /// The minimun number of segments that may exist.
+ /// Perform a full GC.Collect whenever a segment is allocated, and then again after allocation to compact the heap.
+ /// The frequency which segments are checked to see if they're eligible for cleanup.
+ protected ObjectPoolBase(int itemsPerSegment, int minimumSegmentCount, bool gcOnPoolGrowth, int cleanupFrequenceMS)
+ {
+ Initialize(itemsPerSegment, minimumSegmentCount, gcOnPoolGrowth, cleanupFrequenceMS);
+ }
+
+ protected void Initialize(int itemsPerSegment, int minimumSegmentCount, bool gcOnPoolGrowth, int cleanupFrequenceMS)
+ {
+ _itemsPerSegment = itemsPerSegment;
+ _minimumSegmentCount = minimumSegmentCount;
+ _gc = gcOnPoolGrowth;
+
+ // force garbage collection to make sure these new long lived objects
+ // cause as little fragmentation as possible
+ if (_gc)
+ System.GC.Collect();
+
+ lock (_syncRoot)
+ {
+ while (_segments.Count < this.MinimumSegmentCount)
+ {
+ ObjectPoolSegment segment = CreateSegment(false);
+ _segments.Add(segment.SegmentNumber, segment);
+ }
+ }
+
+ // This forces a compact, to make sure our objects fill in any holes in the heap.
+ if (_gc)
+ {
+ System.GC.Collect();
+ }
+
+ _timer = new Timer(CleanupThreadCallback, null, cleanupFrequenceMS, cleanupFrequenceMS);
+ }
+
+ ///
+ /// Forces the segment cleanup algorithm to be run. This method is intended
+ /// primarly for use from the Unit Test libraries.
+ ///
+ internal void ForceCleanup()
+ {
+ CleanupThreadCallback(null);
+ }
+
+ private void CleanupThreadCallback(object state)
+ {
+ if (_disposed)
+ return;
+
+ if (Monitor.TryEnter(_timerLock) == false)
+ return;
+
+ try
+ {
+ lock (_syncRoot)
+ {
+ // If we're below, or at, or minimum segment count threshold,
+ // there's no point in going any further.
+ if (_segments.Count <= _minimumSegmentCount)
+ return;
+
+ for (int i = _activeSegment; i > 0; i--)
+ {
+ ObjectPoolSegment segment;
+ if (_segments.TryGetValue(i, out segment) == true)
+ {
+ // For the "old" segments that were allocated at startup, this will
+ // always be false, as their expiration dates are set at infinity.
+ if (segment.CanBeCleanedUp())
+ {
+ _segments.Remove(i);
+ segment.Dispose();
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ Monitor.Exit(_timerLock);
+ }
+ }
+
+ ///
+ /// Responsible for allocate 1 instance of an object that will be stored in a segment.
+ ///
+ /// An instance of whatever objec the pool is pooling.
+ protected abstract T GetObjectInstance();
+
+
+ private ObjectPoolSegment CreateSegment(bool allowSegmentToBeCleanedUp)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("ObjectPoolBase");
+
+
+ // This method is called inside a lock, so no interlocked stuff required.
+ int segmentToAdd = _activeSegment;
+ _activeSegment++;
+
+ Queue buffers = new Queue();
+ for (int i = 1; i <= this._itemsPerSegment; i++)
+ {
+ T obj = GetObjectInstance();
+ buffers.Enqueue(obj);
+ }
+
+ // certain segments we don't want to ever be cleaned up (the initial segments)
+ DateTime cleanupTime = (allowSegmentToBeCleanedUp) ? DateTime.Now.Add(this._minimumAgeToCleanup) : DateTime.MaxValue;
+ ObjectPoolSegment segment = new ObjectPoolSegment(segmentToAdd, buffers, cleanupTime);
+
+ return segment;
+ }
+
+
+ ///
+ /// Checks in an instance of T owned by the object pool. This method is only intended to be called
+ /// by the WrappedObject class.
+ ///
+ /// The segment from which the instance is checked out.
+ /// The instance of T to check back into the segment.
+ internal void CheckIn(ObjectPoolSegment owningSegment, T instance)
+ {
+ lock (_syncRoot)
+ {
+ owningSegment.CheckInObject(instance);
+ }
+ }
+
+ ///
+ /// Checks an instance of T from the pool. If the pool is not sufficient to
+ /// allow the checkout, a new segment is created.
+ ///
+ /// A WrappedObject around the instance of T. To check
+ /// the instance back into the segment, be sureto dispose the WrappedObject
+ /// when finished.
+ public WrappedObject CheckOut()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("ObjectPoolBase");
+
+ // It's key that this CheckOut always, always, uses a pooled object
+ // from the oldest available segment. This will help keep the "newer"
+ // segments from being used - which in turn, makes them eligible
+ // for deletion.
+
+
+ lock (_syncRoot)
+ {
+ ObjectPoolSegment targetSegment = null;
+
+ // find the oldest segment that has items available for checkout
+ for (int i = 0; i < _activeSegment; i++)
+ {
+ ObjectPoolSegment segment;
+ if (_segments.TryGetValue(i, out segment) == true)
+ {
+ if (segment.AvailableItems > 0)
+ {
+ targetSegment = segment;
+ break;
+ }
+ }
+ }
+
+ if (targetSegment == null)
+ {
+ // We couldn't find a sigment that had any available space in it,
+ // so it's time to create a new segment.
+
+ // Before creating the segment, do a GC to make sure the heap
+ // is compacted.
+ if (_gc) GC.Collect();
+
+ targetSegment = CreateSegment(true);
+
+ if (_gc) GC.Collect();
+
+ _segments.Add(targetSegment.SegmentNumber, targetSegment);
+ }
+
+ WrappedObject obj = new WrappedObject(this, targetSegment, targetSegment.CheckOutObject());
+ return obj;
+ }
+ }
+
+ ///
+ /// The total number of segments created. Intended to be used by the Unit Tests.
+ ///
+ public int TotalSegments
+ {
+ get
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("ObjectPoolBase");
+
+ lock (_syncRoot)
+ {
+ return _segments.Count;
+ }
+ }
+ }
+
+ ///
+ /// The number of items that are in a segment. Items in a segment
+ /// are all allocated at the same time, and are hopefully close to
+ /// each other in the managed heap.
+ ///
+ public int ItemsPerSegment
+ {
+ get
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("ObjectPoolBase");
+
+ return _itemsPerSegment;
+ }
+ }
+
+ ///
+ /// The minimum number of segments. When segments are reclaimed,
+ /// this number of segments will always be left alone. These
+ /// segments are allocated at startup.
+ ///
+ public int MinimumSegmentCount
+ {
+ get
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("ObjectPoolBase");
+
+ return _minimumSegmentCount;
+ }
+ }
+
+ ///
+ /// The age a segment must be before it's eligible for cleanup.
+ /// This is used to prevent thrash, and typical values are in
+ /// the 5 minute range.
+ ///
+ public TimeSpan MinimumSegmentAgePriorToCleanup
+ {
+ get
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("ObjectPoolBase");
+
+ return _minimumAgeToCleanup;
+ }
+ set
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("ObjectPoolBase");
+
+ _minimumAgeToCleanup = value;
+ }
+ }
+
+ ///
+ /// The frequence which the cleanup thread runs. This is typically
+ /// expected to be in the 5 minute range.
+ ///
+ public int CleanupFrequencyMilliseconds
+ {
+ get
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("ObjectPoolBase");
+
+ return _cleanupFrequency;
+ }
+ set
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("ObjectPoolBase");
+
+ Interlocked.Exchange(ref _cleanupFrequency, value);
+
+ _timer.Change(_cleanupFrequency, _cleanupFrequency);
+ }
+ }
+
+ #region IDisposable Members
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ Dispose(true);
+
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ lock (_syncRoot)
+ {
+ if (_disposed)
+ return;
+
+ _timer.Dispose();
+ _disposed = true;
+
+ foreach (KeyValuePair> kvp in _segments)
+ {
+ try
+ {
+ kvp.Value.Dispose();
+ }
+ catch (Exception) { }
+ }
+
+ _segments.Clear();
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ internal class ObjectPoolSegment : IDisposable where T : class
+ {
+ private Queue _liveInstances = new Queue();
+ private int _segmentNumber;
+ private int _originalCount;
+ private bool _isDisposed = false;
+ private DateTime _eligibleForDeletionAt;
+
+ public int SegmentNumber { get { return _segmentNumber; } }
+ public int AvailableItems { get { return _liveInstances.Count; } }
+ public DateTime DateEligibleForDeletion { get { return _eligibleForDeletionAt; } }
+
+ public ObjectPoolSegment(int segmentNumber, Queue liveInstances, DateTime eligibleForDeletionAt)
+ {
+ _segmentNumber = segmentNumber;
+ _liveInstances = liveInstances;
+ _originalCount = liveInstances.Count;
+ _eligibleForDeletionAt = eligibleForDeletionAt;
+ }
+
+ public bool CanBeCleanedUp()
+ {
+ if (_isDisposed == true)
+ throw new ObjectDisposedException("ObjectPoolSegment");
+
+ return ((_originalCount == _liveInstances.Count) && (DateTime.Now > _eligibleForDeletionAt));
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed)
+ return;
+
+ _isDisposed = true;
+
+ bool shouldDispose = (typeof(T) is IDisposable);
+ while (_liveInstances.Count != 0)
+ {
+ T instance = _liveInstances.Dequeue();
+ if (shouldDispose)
+ {
+ try
+ {
+ (instance as IDisposable).Dispose();
+ }
+ catch (Exception) { }
+ }
+ }
+ }
+
+ internal void CheckInObject(T o)
+ {
+ if (_isDisposed == true)
+ throw new ObjectDisposedException("ObjectPoolSegment");
+
+ _liveInstances.Enqueue(o);
+ }
+
+ internal T CheckOutObject()
+ {
+ if (_isDisposed == true)
+ throw new ObjectDisposedException("ObjectPoolSegment");
+
+ if (0 == _liveInstances.Count)
+ throw new InvalidOperationException("No Objects Available for Checkout");
+
+ T o = _liveInstances.Dequeue();
+ return o;
+ }
+ }
+}
diff --git a/libsecondlife/ParcelManager.cs b/libsecondlife/ParcelManager.cs
index 9bcc1854..2aa5989f 100644
--- a/libsecondlife/ParcelManager.cs
+++ b/libsecondlife/ParcelManager.cs
@@ -739,6 +739,48 @@ namespace libsecondlife
Client.Network.SendPacket(request, simulator);
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void ParcelSubdivide(Simulator simulator, float west, float south, float east, float north)
+ {
+ ParcelDividePacket divide = new ParcelDividePacket();
+ divide.AgentData.AgentID = Client.Network.AgentID;
+ divide.AgentData.SessionID = Client.Network.SessionID;
+ divide.ParcelData.East = east;
+ divide.ParcelData.North = north;
+ divide.ParcelData.South = south;
+ divide.ParcelData.West = west;
+
+ Client.Network.SendPacket(divide, simulator);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void ParcelJoin(Simulator simulator, float west, float south, float east, float north)
+ {
+ ParcelJoinPacket join = new ParcelJoinPacket();
+ join.AgentData.AgentID = Client.Network.AgentID;
+ join.AgentData.SessionID = Client.Network.SessionID;
+ join.ParcelData.East = east;
+ join.ParcelData.North = north;
+ join.ParcelData.South = south;
+ join.ParcelData.West = west;
+
+ Client.Network.SendPacket(join, simulator);
+ }
+
#endregion Public Methods
#region Packet Handlers
diff --git a/libsecondlife/README.txt b/libsecondlife/README.txt
deleted file mode 100644
index ccf5f1e5..00000000
--- a/libsecondlife/README.txt
+++ /dev/null
@@ -1,47 +0,0 @@
-libsecondlife - A Second Life networking library
-------------------------------------------------
-
-
-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.
-
-
-Building libsecondlife
-----------------------
-
-libsecondlife has two primary build systems, NAnt and Microsoft Visual Studio.
-
-To use NAnt first download it from http://nant.sourceforge.net/ and then
-simply run "nant" in the top directory containing libsecondlife.build, or run
-"nant package" to build a zip file containing the executables and libraries.
-
-To use the Visual Studio project files, you must be running Visual Studio 2005
-or compatible software (including Visual C# Express 2005 and SharpDevelop,
-possibly MonoDevelop). Simply open the libsecondlife.sln solution and begin
-compiling. The output will be in the bin folder under the root directory.
-
-If you have any questions, stop by #libsl on EFNet in IRC
-
-http://www.libsecondlife.org/
-
diff --git a/libsecondlife/SecondLife.cs b/libsecondlife/SecondLife.cs
index 81cf18cd..a713b950 100644
--- a/libsecondlife/SecondLife.cs
+++ b/libsecondlife/SecondLife.cs
@@ -25,10 +25,6 @@
*/
using System;
-using System.Threading;
-using libsecondlife.Packets;
-using libsecondlife.AssetSystem;
-using libsecondlife.InventorySystem;
namespace libsecondlife
{
@@ -65,13 +61,11 @@ namespace libsecondlife
/// Group Subsystem
public GroupManager Groups;
/// Asset Subsystem
- public libsecondlife.AssetSystem.AssetManager Assets;
+ public AssetManager Assets;
/// Appearance Subsystem
- public libsecondlife.AssetSystem.AppearanceManager Appearance;
+ public AppearanceManager Appearance;
/// Inventory Subsystem
- public libsecondlife.InventorySystem.InventoryManager Inventory;
- /// Image Subsystem
- public ImageManager Images;
+ public InventoryManager Inventory;
/// Directory searches including classifieds, people, land
/// sales, etc
public DirectoryManager Directory;
@@ -104,9 +98,8 @@ namespace libsecondlife
Grid = new GridManager(this);
Objects = new ObjectManager(this);
Groups = new GroupManager(this);
- Assets = new libsecondlife.AssetSystem.AssetManager(this);
- Appearance = new libsecondlife.AssetSystem.AppearanceManager(this);
- Images = new ImageManager(this);
+ Assets = new AssetManager(this);
+ Appearance = new AppearanceManager(this, Assets);
Inventory = new InventoryManager(this);
Directory = new DirectoryManager(this);
Terrain = new TerrainManager(this);
@@ -170,5 +163,33 @@ namespace libsecondlife
}
}
}
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void LogStatic(string message, Helpers.LogLevel level)
+ {
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void LogStatic(string message, Helpers.LogLevel level, Exception exception)
+ {
+ }
+
+ ///
+ ///
+ ///
+ ///
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void DebugLogStatic(string message)
+ {
+ }
}
}
diff --git a/libsecondlife/Settings.cs b/libsecondlife/Settings.cs
index 56a188ea..05f28f03 100644
--- a/libsecondlife/Settings.cs
+++ b/libsecondlife/Settings.cs
@@ -42,9 +42,10 @@ namespace libsecondlife
/// The version of libsecondlife (not the SL protocol itself)
public string VERSION = "libsecondlife 0.0.9";
/// XML-RPC login server to connect to
- public string LOGIN_SERVER = "https://login.agni.lindenlab.com/cgi-bin/login.cgi";
+ public string LOGIN_SERVER = "https://login.aditi.lindenlab.com/cgi-bin/login.cgi";
+
+ // Timeouts
- /// Timeouts:
/// Number of milliseconds before a teleport attempt will time
/// out
public int TELEPORT_TIMEOUT = 40 * 1000;
@@ -67,7 +68,7 @@ namespace libsecondlife
/// the grid interface
public int MAP_REQUEST_TIMEOUT = 5 * 1000;
- /// Sizes
+ // Sizes
/// The initial size of the packet inbox, where packets are
/// stored before processing
@@ -98,20 +99,23 @@ namespace libsecondlife
/// Network stats queue length (seconds)
public int STATS_QUEUE_SIZE = 5;
- /// Configuration options (mostly booleans)
- /// Whether or not to process packet callbacks async. This is
- /// better off being true, but the option exists to set it to false and
- /// use the old behavior. Please fix your packet callback to return to
- /// the pump rather than just setting this back to false, if you can
+ // Configuration options (mostly booleans)
+
+ /// Enable to process packets synchronously, where all of the
+ /// callbacks for each packet must return before the next packet is
+ /// processed
/// This is an experimental feature and is not completely
- /// reliable yet
+ /// reliable yet. Ideally it would reduce context switches and thread
+ /// overhead, but several calls currently block for a long time and
+ /// would need to be rewritten as asynchronous code before this is
+ /// feasible
public bool SYNC_PACKETCALLBACKS = false;
/// Enable/disable debugging log messages
public bool DEBUG = true;
/// Attach avatar names to log messages
public bool LOG_NAMES = true;
- /// Log packet retransmission info
- public bool LOG_RESENDS = true;
+ /// Log packet retransmission info
+ public bool LOG_RESENDS = true;
/// Enable/disable storing terrain heightmaps in the
/// TerrainManager
public bool STORE_LAND_PATCHES = false;
@@ -146,8 +150,15 @@ namespace libsecondlife
/// Whether to establish connections to HTTP capabilities
/// servers for simulators
public bool ENABLE_CAPS = true;
- /// Whether to decode sim stats
- public bool ENABLE_SIMSTATS = true;
+ /// Whether to decode sim stats
+ public bool ENABLE_SIMSTATS = true;
+ /// The capabilities servers are currently designed to
+ /// periodically return a 502 error which signals for the client to
+ /// 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
+ /// libsecondlife is not aware of will automatically be fetched.
+ public bool FETCH_MISSING_INVENTORY = true;
/// Cost of uploading an asset
/// Read-only since this value is dynamically fetched at login
diff --git a/libsecondlife/Simulator.cs b/libsecondlife/Simulator.cs
index 7c72cb38..f55dca6f 100644
--- a/libsecondlife/Simulator.cs
+++ b/libsecondlife/Simulator.cs
@@ -34,10 +34,9 @@ using libsecondlife.Packets;
namespace libsecondlife
{
///
- /// Simulator is a wrapper for a network connection to a simulator and the
- /// Region class representing the block of land in the metaverse
+ ///
///
- public class Simulator
+ public class Simulator : UDPBase
{
#region Enums
@@ -232,29 +231,38 @@ namespace libsecondlife
public int MissedPings = 0;
/// Current time dilation of this simulator
public float Dilation = 0;
- public int FPS = 0;
- public float PhysicsFPS = 0;
- public float AgentUpdates = 0;
- public float FrameTime = 0;
- public float NetTime = 0;
- public float PhysicsTime = 0;
- public float ImageTime = 0;
- public float ScriptTime = 0;
- public float OtherTime = 0;
- public int Objects = 0;
- public int ScriptedObjects = 0;
- public int Agents = 0;
- public int ChildAgents = 0;
- public int ActiveScripts = 0;
- public int LSLIPS = 0;
- public int INPPS = 0;
- public int OUTPPS = 0;
- public int PendingDownloads = 0;
- public int PendingUploads = 0;
- public int VirtualSize = 0;
- public int ResidentSize = 0;
- public int PendingLocalUploads = 0;
- public int UnackedBytes = 0;
+ public int FPS = 0;
+ public float PhysicsFPS = 0;
+ public float AgentUpdates = 0;
+ public float FrameTime = 0;
+ public float NetTime = 0;
+ public float PhysicsTime = 0;
+ public float ImageTime = 0;
+ public float ScriptTime = 0;
+ public float OtherTime = 0;
+ public int Objects = 0;
+ public int ScriptedObjects = 0;
+ public int Agents = 0;
+ public int ChildAgents = 0;
+ public int ActiveScripts = 0;
+ public int LSLIPS = 0;
+ public int INPPS = 0;
+ public int OUTPPS = 0;
+ public int PendingDownloads = 0;
+ public int PendingUploads = 0;
+ public int VirtualSize = 0;
+ public int ResidentSize = 0;
+ public int PendingLocalUploads = 0;
+ public int UnackedBytes = 0;
+
+ /// Used to obtain a lock on the sequence number for packets
+ /// sent to this simulator. Only useful for applications manipulating
+ /// sequence numbers
+ public object SequenceLock = new object();
+ /// The current sequence number for packets sent to this
+ /// simulator. Must be locked with SequenceLock before modifying. Only
+ /// useful for applications manipulating sequence numbers
+ public volatile uint Sequence = 0;
#endregion Public Members
@@ -287,56 +295,43 @@ namespace libsecondlife
internal Queue PacketArchive;
/// Packets we sent out that need ACKs from the simulator
internal Dictionary NeedAck = new Dictionary();
-
+
private NetworkManager Network;
- private uint Sequence = 0;
- private object SequenceLock = new object();
- private byte[] RecvBuffer = new byte[4096];
- private byte[] ZeroBuffer = new byte[8192];
- private byte[] ZeroOutBuffer = new byte[4096];
- private Socket Connection = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- private AsyncCallback ReceivedData;
private Queue InBytes, OutBytes;
// ACKs that are queued up to be sent to the simulator
private SortedList PendingAcks = new SortedList();
private IPEndPoint ipEndPoint;
- private EndPoint endPoint;
private System.Timers.Timer AckTimer;
private System.Timers.Timer PingTimer;
private System.Timers.Timer StatsTimer;
#endregion Internal/Private Members
-
///
- /// Default constructor
+ ///
///
/// Reference to the SecondLife client
- /// IP address and port of the simulator
- public Simulator(SecondLife client, IPEndPoint address)
+ ///
+ public Simulator(SecondLife client, IPEndPoint address, ulong handle)
+ : base(address)
{
Client = client;
+ ipEndPoint = address;
+ Handle = handle;
Estate = new EstateTools(Client);
- Network = client.Network;
+ Network = Client.Network;
PacketArchive = new Queue(Settings.PACKET_ARCHIVE_SIZE);
InBytes = new Queue(Client.Settings.STATS_QUEUE_SIZE);
OutBytes = new Queue(Client.Settings.STATS_QUEUE_SIZE);
- // Create an endpoint that we will be communicating with (need it in two
- // types due to .NET weirdness)
- ipEndPoint = address;
- endPoint = (EndPoint)ipEndPoint;
-
- // Initialize the callback for receiving a new packet
- ReceivedData = new AsyncCallback(OnReceivedData);
-
+ // Timer for sending out queued packet acknowledgements
AckTimer = new System.Timers.Timer(Settings.NETWORK_TICK_LENGTH);
AckTimer.Elapsed += new System.Timers.ElapsedEventHandler(AckTimer_Elapsed);
-
+ // Timer for recording simulator connection statistics
StatsTimer = new System.Timers.Timer(1000);
StatsTimer.Elapsed += new System.Timers.ElapsedEventHandler(StatsTimer_Elapsed);
-
+ // Timer for periodically pinging the simulator
PingTimer = new System.Timers.Timer(Settings.PING_INTERVAL);
PingTimer.Elapsed += new System.Timers.ElapsedEventHandler(PingTimer_Elapsed);
}
@@ -366,9 +361,8 @@ namespace libsecondlife
{
ConnectedEvent.Reset();
- // Associate this simulator's socket with the given ip/port and start listening
- Connection.Connect(endPoint);
- Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null);
+ // Create the UDP connection
+ Start();
// Mark ourselves as connected before firing everything else up
connected = true;
@@ -392,7 +386,7 @@ namespace libsecondlife
if (!ConnectedEvent.WaitOne(Client.Settings.SIMULATOR_TIMEOUT, false))
{
- Client.Log("Giving up on waiting for RegionHandshake for " + this.ToString(),
+ Client.Log("Giving up on waiting for RegionHandshake for " + this.ToString(),
Helpers.LogLevel.Warning);
}
@@ -406,58 +400,59 @@ namespace libsecondlife
return false;
}
- public void setSeedCaps(string seedcaps) {
- if(SimCaps != null) {
- if(SimCaps.Seedcaps == seedcaps) return;
+ public void SetSeedCaps(string seedcaps)
+ {
+ if (SimCaps != null)
+ {
+ if (SimCaps.Seedcaps == seedcaps) return;
- Client.Log("Unexpected change of seed capability", Helpers.LogLevel.Warning);
- SimCaps.Disconnect(true);
- SimCaps = null;
- }
-
- if (Client.Settings.ENABLE_CAPS) // [TODO] Implement caps
- {
- // Connect to the new CAPS system
- if (!String.IsNullOrEmpty(seedcaps))
- SimCaps = new Caps(Client, this, seedcaps);
- else
- Client.Log("Setting up a sim without a valid capabilities server!", Helpers.LogLevel.Error);
- }
-
- }
+ Client.Log("Unexpected change of seed capability", Helpers.LogLevel.Warning);
+ SimCaps.Disconnect(true);
+ SimCaps = null;
+ }
+
+ if (Client.Settings.ENABLE_CAPS) // [TODO] Implement caps
+ {
+ // Connect to the new CAPS system
+ if (!String.IsNullOrEmpty(seedcaps))
+ SimCaps = new Caps(Client, this, seedcaps);
+ else
+ Client.Log("Setting up a sim without a valid capabilities server!", Helpers.LogLevel.Error);
+ }
+
+ }
///
/// Disconnect from this simulator
///
public void Disconnect()
{
- connected = false;
- AckTimer.Stop();
- StatsTimer.Stop();
- if (Client.Settings.SEND_PINGS) PingTimer.Stop();
-
- // Kill the current CAPS system
- if (SimCaps != null)
+ if (connected)
{
- SimCaps.Disconnect(true);
- SimCaps = null;
+ connected = false;
+
+ AckTimer.Stop();
+ StatsTimer.Stop();
+ if (Client.Settings.SEND_PINGS) PingTimer.Stop();
+
+ // Kill the current CAPS system
+ if (SimCaps != null)
+ {
+ SimCaps.Disconnect(true);
+ SimCaps = null;
+ }
+
+ // Try to send the CloseCircuit notice
+ CloseCircuitPacket close = new CloseCircuitPacket();
+ UDPPacketBuffer buf = new UDPPacketBuffer(ipEndPoint, false);
+ buf.Data = close.ToBytes();
+ buf.DataLength = buf.Data.Length;
+
+ AsyncBeginSend(buf);
+
+ // Shut the socket communication down
+ Stop();
}
-
-
- // Make sure the socket is hooked up
- if (!Connection.Connected) return;
-
- // Try to send the CloseCircuit notice
- CloseCircuitPacket close = new CloseCircuitPacket();
-
- // There's a high probability of this failing if the network is
- // disconnecting, so don't even bother logging the error
- try { Connection.Send(close.ToBytes()); }
- catch (SocketException) { }
-
- // Shut the socket communication down
- try { Connection.Shutdown(SocketShutdown.Both); }
- catch (SocketException) { }
}
///
@@ -482,13 +477,13 @@ namespace libsecondlife
Sequence = 1;
else
Sequence++;
+
packet.Header.Sequence = Sequence;
}
// Scrub any appended ACKs since all of the ACK handling is done here
if (packet.Header.AckList.Length > 0)
packet.Header.AckList = new uint[0];
-
packet.Header.AppendedAcks = false;
if (packet.Header.Reliable)
@@ -533,30 +528,25 @@ namespace libsecondlife
SentBytes += (ulong)bytes;
SentPackets++;
- try
- {
- // Zerocode if needed
- if (packet.Header.Zerocoded)
- {
- lock (ZeroOutBuffer)
- {
- bytes = Helpers.ZeroEncode(buffer, bytes, ZeroOutBuffer);
- Connection.Send(ZeroOutBuffer, bytes, SocketFlags.None);
- }
- }
- else
- {
- Connection.Send(buffer, bytes, SocketFlags.None);
- }
- }
- catch (SocketException)
- {
- Client.Log("Tried to send a " + packet.Type.ToString() + " on a closed socket, shutting down " +
- this.ToString(), Helpers.LogLevel.Info);
+ UDPPacketBuffer buf;
- Network.DisconnectSim(this);
- return;
+ // Zerocode if needed
+ if (packet.Header.Zerocoded)
+ {
+ buf = new UDPPacketBuffer(ipEndPoint, true, false);
+
+ bytes = Helpers.ZeroEncode(buffer, bytes, buf.Data);
+ buf.DataLength = bytes;
}
+ else
+ {
+ buf = new UDPPacketBuffer(ipEndPoint, false, false);
+
+ buf.Data = buffer;
+ buf.DataLength = bytes;
+ }
+
+ AsyncBeginSend(buf);
}
///
@@ -584,7 +574,12 @@ namespace libsecondlife
SentBytes += (ulong)payload.Length;
SentPackets++;
- Connection.Send(payload, payload.Length, SocketFlags.None);
+
+ UDPPacketBuffer buf = new UDPPacketBuffer(ipEndPoint, false);
+ buf.Data = payload;
+ buf.DataLength = payload.Length;
+
+ AsyncBeginSend(buf);
}
catch (SocketException)
{
@@ -596,6 +591,9 @@ namespace libsecondlife
}
}
+ ///
+ ///
+ ///
public void SendPing()
{
StartPingCheckPacket ping = new StartPingCheckPacket();
@@ -606,46 +604,6 @@ namespace libsecondlife
LastPingSent = Environment.TickCount;
}
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public void ParcelSubdivide(float west, float south, float east, float north)
- {
- ParcelDividePacket divide = new ParcelDividePacket();
- divide.AgentData.AgentID = Client.Network.AgentID;
- divide.AgentData.SessionID = Client.Network.SessionID;
- divide.ParcelData.East = east;
- divide.ParcelData.North = north;
- divide.ParcelData.South = south;
- divide.ParcelData.West = west;
-
- SendPacket(divide, true);
- }
-
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public void ParcelJoin(float west, float south, float east, float north)
- {
- ParcelJoinPacket join = new ParcelJoinPacket();
- join.AgentData.AgentID = Client.Network.AgentID;
- join.AgentData.SessionID = Client.Network.SessionID;
- join.ParcelData.East = east;
- join.ParcelData.North = north;
- join.ParcelData.South = south;
- join.ParcelData.West = west;
-
- SendPacket(join, true);
- }
-
///
/// Returns Simulator Name as a String
///
@@ -664,7 +622,7 @@ namespace libsecondlife
///
public override int GetHashCode()
{
- return ID.GetHashCode();
+ return Handle.GetHashCode();
}
///
@@ -702,6 +660,66 @@ namespace libsecondlife
return !(lhs == rhs);
}
+ protected override void PacketReceived(UDPPacketBuffer buffer)
+ {
+ Packet packet = null;
+
+ // Update the disconnect flag so this sim doesn't time out
+ DisconnectCandidate = false;
+
+ #region Packet Decoding
+
+ int packetEnd = buffer.DataLength - 1;
+ packet = Packet.BuildPacket(buffer.Data, ref packetEnd, buffer.ZeroData);
+
+ // Fail-safe check
+ if (packet == null)
+ {
+ Client.Log("Couldn't build a message from the incoming data", Helpers.LogLevel.Warning);
+ return;
+ }
+
+ RecvBytes += (ulong)buffer.DataLength;
+ RecvPackets++;
+
+ #endregion Packet Decoding
+
+ #region Reliable Handling
+
+ if (packet.Header.Reliable)
+ {
+ // Add this packet to the list of ACKs that need to be sent out
+ lock (PendingAcks)
+ {
+ uint sequence = (uint)packet.Header.Sequence;
+ if (!PendingAcks.ContainsKey(sequence)) PendingAcks[sequence] = sequence;
+ }
+
+ // Send out ACKs if we have a lot of them
+ if (PendingAcks.Count >= Client.Settings.MAX_PENDING_ACKS)
+ SendAcks();
+
+ if (packet.Header.Resent) ++ReceivedResends;
+ }
+
+ #endregion Reliable Handling
+
+ #region Inbox Insertion
+
+ NetworkManager.IncomingPacket incomingPacket;
+ incomingPacket.Simulator = this;
+ incomingPacket.Packet = packet;
+
+ // TODO: Prioritize the queue
+ Network.PacketInbox.Enqueue(incomingPacket);
+
+ #endregion Inbox Insertion
+ }
+
+ protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent)
+ {
+ }
+
private void DoThrottle()
{
int throttle;
@@ -765,9 +783,9 @@ namespace libsecondlife
{
try
{
- if (Client.Settings.LOG_RESENDS)
- Client.DebugLog(String.Format("Resending packet #{0}, {1}ms have passed",
- packet.Header.Sequence, now - packet.TickCount));
+ if (Client.Settings.LOG_RESENDS)
+ Client.DebugLog(String.Format("Resending packet #{0}, {1}ms have passed",
+ packet.Header.Sequence, now - packet.TickCount));
packet.Header.Resent = true;
++ResentPackets;
@@ -782,85 +800,6 @@ namespace libsecondlife
}
}
- ///
- /// Callback handler for incomming data
- ///
- ///
- private void OnReceivedData(IAsyncResult result)
- {
- Packet packet = null;
- int numBytes;
-
- // Update the disconnect flag so this sim doesn't time out
- DisconnectCandidate = false;
-
- #region Packet Decoding
-
- lock (RecvBuffer)
- {
- // Retrieve the incoming packet
- try
- {
- numBytes = Connection.EndReceiveFrom(result, ref endPoint);
-
- int packetEnd = numBytes - 1;
- packet = Packet.BuildPacket(RecvBuffer, ref packetEnd, ZeroBuffer);
-
- Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null);
- }
- catch (SocketException)
- {
- Client.Log(endPoint.ToString() + " socket is closed, shutting down " + this.ToString(),
- Helpers.LogLevel.Info);
- Network.DisconnectSim(this);
- return;
- }
- }
-
- // Fail-safe check
- if (packet == null)
- {
- Client.Log("Couldn't build a message from the incoming data", Helpers.LogLevel.Warning);
- return;
- }
-
- RecvBytes += (ulong)numBytes;
- RecvPackets++;
-
- #endregion Packet Decoding
-
- #region Reliable Handling
-
- if (packet.Header.Reliable)
- {
- // Queue up ACKs for resent packets
- lock (PendingAcks)
- {
- uint sequence = (uint)packet.Header.Sequence;
- if (!PendingAcks.ContainsKey(sequence)) PendingAcks[sequence] = sequence;
- }
-
- // Send out ACKs if we have a lot of them
- if (PendingAcks.Count >= Client.Settings.MAX_PENDING_ACKS)
- SendAcks();
-
- if (packet.Header.Resent) ++ReceivedResends;
- }
-
- #endregion Reliable Handling
-
- #region Inbox Insertion
-
- NetworkManager.IncomingPacket incomingPacket;
- incomingPacket.Simulator = this;
- incomingPacket.Packet = packet;
-
- // TODO: Prioritize the queue
- Network.PacketInbox.Enqueue(incomingPacket);
-
- #endregion Inbox Insertion
- }
-
private void AckTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ea)
{
SendAcks();
diff --git a/libsecondlife/Types.cs b/libsecondlife/Types.cs
index 00cf81dd..c705fc0f 100644
--- a/libsecondlife/Types.cs
+++ b/libsecondlife/Types.cs
@@ -471,18 +471,6 @@ namespace libsecondlife
///
public static bool operator==(LLVector3 lhs, LLVector3 rhs)
{
- // If both are null, or both are same instance, return true
- if (System.Object.ReferenceEquals(lhs, rhs))
- {
- return true;
- }
-
- // If one is null, but not both, return false.
- if (((object)lhs == null) || ((object)rhs == null))
- {
- return false;
- }
-
return (lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z);
}
@@ -661,18 +649,6 @@ namespace libsecondlife
///
public static bool operator ==(LLVector3d lhs, LLVector3d rhs)
{
- // If both are null, or both are same instance, return true
- if (System.Object.ReferenceEquals(lhs, rhs))
- {
- return true;
- }
-
- // If one is null, but not both, return false.
- if (((object)lhs == null) || ((object)rhs == null))
- {
- return false;
- }
-
return (lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z);
}
@@ -1092,18 +1068,6 @@ namespace libsecondlife
///
public static bool operator ==(LLQuaternion lhs, LLQuaternion rhs)
{
- // If both are null, or both are same instance, return true
- if (System.Object.ReferenceEquals(lhs, rhs))
- {
- return true;
- }
-
- // If one is null, but not both, return false.
- if (((object)lhs == null) || ((object)rhs == null))
- {
- return false;
- }
-
// Return true if the fields match:
return lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z && lhs.W == rhs.W;
}
diff --git a/libsecondlife/UDPBase.cs b/libsecondlife/UDPBase.cs
new file mode 100644
index 00000000..6e6e844e
--- /dev/null
+++ b/libsecondlife/UDPBase.cs
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 2006, Clutch, Inc.
+ * Original Author: Jeff Cesnik
+ * 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.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace libsecondlife
+{
+ // this class encapsulates a single packet that
+ // is either sent or received by a UDP socket
+ public class UDPPacketBuffer
+ {
+ // size of the buffer
+ public const int BUFFER_SIZE = 1536;
+ /// Size of the temporary buffer for zerodecoding and
+ /// zeroencoding this packet
+ public const int ZERO_BUFFER_SIZE = 4096;
+ // the buffer itself
+ public byte[] Data;
+ /// Temporary buffer used for zerodecoding and zeroencoding
+ /// this packet
+ public byte[] ZeroData;
+ // length of data to transmit
+ public int DataLength;
+ // the (IP)Endpoint of the remote host
+ // this will be filled in by the call to udpSocket.BeginReceiveFrom
+ public EndPoint RemoteEndPoint;
+
+ ///
+ /// Create an allocated UDP packet buffer for receiving a packet
+ ///
+ public UDPPacketBuffer()
+ {
+ Data = new byte[UDPPacketBuffer.BUFFER_SIZE];
+ ZeroData = new byte[UDPPacketBuffer.ZERO_BUFFER_SIZE];
+ // Will be modified later by BeginReceiveFrom()
+ RemoteEndPoint = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
+ }
+
+ public UDPPacketBuffer(IPEndPoint endPoint, bool allocate)
+ {
+ if (allocate) Data = new byte[UDPPacketBuffer.BUFFER_SIZE];
+ ZeroData = new byte[UDPPacketBuffer.ZERO_BUFFER_SIZE];
+ RemoteEndPoint = (EndPoint)endPoint;
+ }
+
+ public UDPPacketBuffer(EndPoint endPoint, bool allocate, bool allocateZero)
+ {
+ if (allocate) Data = new byte[UDPPacketBuffer.BUFFER_SIZE];
+ if (allocateZero) ZeroData = new byte[UDPPacketBuffer.ZERO_BUFFER_SIZE];
+ RemoteEndPoint = endPoint;
+ }
+ }
+
+ public class PacketBufferPool : ObjectPoolBase
+ {
+ private IPEndPoint EndPoint;
+
+ ///
+ /// Initialize the object pool in client mode
+ ///
+ /// Server to connect to
+ ///
+ ///
+ public PacketBufferPool(IPEndPoint endPoint, int itemsPerSegment, int minSegments)
+ : base()
+ {
+ EndPoint = endPoint;
+ Initialize(itemsPerSegment, minSegments, true, 1000 * 60 * 5);
+ }
+
+ ///
+ /// Initialize the object pool in server mode
+ ///
+ ///
+ ///
+ ///
+ public PacketBufferPool(int itemsPerSegment, int minSegments)
+ : base()
+ {
+ EndPoint = null;
+ Initialize(itemsPerSegment, minSegments, true, 1000 * 60 * 5);
+ }
+
+ protected override UDPPacketBuffer GetObjectInstance()
+ {
+ if (EndPoint != null)
+ return new UDPPacketBuffer(EndPoint, true);
+ else
+ return new UDPPacketBuffer();
+ }
+ }
+
+ ///
+ ///
+ ///
+ public abstract class UDPBase
+ {
+ // these abstract methods must be implemented in a derived class to actually do
+ // something with the packets that are sent and received.
+ protected abstract void PacketReceived(UDPPacketBuffer buffer);
+ protected abstract void PacketSent(UDPPacketBuffer buffer, int bytesSent);
+
+ // the port to listen on
+ protected int udpPort;
+
+ // the UDP socket
+ private Socket udpSocket;
+
+ //private PacketBufferPool _bufferPool;
+
+ // the ReaderWriterLock is used solely for the purposes of shutdown (Stop()).
+ // since there are potentially many "reader" threads in the internal .NET IOCP
+ // thread pool, this is a cheaper synchronization primitive than using
+ // a Mutex object. This allows many UDP socket "reads" concurrently - when
+ // Stop() is called, it attempts to obtain a writer lock which will then
+ // wait until all outstanding operations are completed before shutting down.
+ // this avoids the problem of closing the socket with outstanding operations
+ // and trying to catch the inevitable ObjectDisposedException.
+ private ReaderWriterLock rwLock = new ReaderWriterLock();
+
+ // number of outstanding operations. This is a reference count
+ // which we use to ensure that the threads exit cleanly. Note that
+ // we need this because the threads will potentially still need to process
+ // data even after the socket is closed.
+ private int rwOperationCount = 0;
+
+ // the all important shutdownFlag. This is synchronized through the ReaderWriterLock.
+ private bool shutdownFlag = true;
+
+ //
+ private IPEndPoint remoteEndPoint = null;
+
+
+ ///
+ /// Initialize the UDP packet handler in server mode
+ ///
+ /// Port to listening for incoming UDP packets on
+ public UDPBase(int port)
+ {
+ udpPort = port;
+ //_bufferPool = new PacketBufferPool(udpPort, 64, 1);
+ }
+
+ ///
+ /// Initialize the UDP packet handler in client mode
+ ///
+ /// Remote UDP server to connect to
+ public UDPBase(IPEndPoint endPoint)
+ {
+ remoteEndPoint = endPoint;
+ udpPort = 0;
+ //_bufferPool = new PacketBufferPool(endPoint, 64, 1);
+ }
+
+ ///
+ ///
+ ///
+ public void Start()
+ {
+ if (shutdownFlag)
+ {
+ if (remoteEndPoint == null)
+ {
+ // Server mode
+
+ // create and bind the socket
+ IPEndPoint ipep = new IPEndPoint(IPAddress.Any, udpPort);
+ udpSocket = new Socket(
+ AddressFamily.InterNetwork,
+ SocketType.Dgram,
+ ProtocolType.Udp);
+ udpSocket.Bind(ipep);
+ }
+ else
+ {
+ // Client mode
+
+ udpSocket = new Socket(
+ AddressFamily.InterNetwork,
+ SocketType.Dgram,
+ ProtocolType.Udp);
+ udpSocket.Connect(remoteEndPoint);
+ }
+
+ // we're not shutting down, we're starting up
+ shutdownFlag = false;
+
+ // kick off an async receive. The Start() method will return, the
+ // actual receives will occur asynchronously and will be caught in
+ // AsyncEndRecieve().
+ AsyncBeginReceive();
+ }
+ }
+
+ ///
+ ///
+ ///
+ public void Stop()
+ {
+ if (!shutdownFlag)
+ {
+ // wait indefinitely for a writer lock. Once this is called, the .NET runtime
+ // will deny any more reader locks, in effect blocking all other send/receive
+ // threads. Once we have the lock, we set shutdownFlag to inform the other
+ // threads that the socket is closed.
+ rwLock.AcquireWriterLock(-1);
+ shutdownFlag = true;
+ udpSocket.Close();
+ rwLock.ReleaseWriterLock();
+
+ // wait for any pending operations to complete on other
+ // threads before exiting.
+ while (rwOperationCount > 0)
+ Thread.Sleep(1);
+ }
+ }
+
+ ///
+ ///
+ ///
+ public bool IsRunning
+ {
+ get { return !shutdownFlag; }
+ }
+
+ private void AsyncBeginReceive()
+ {
+ // this method actually kicks off the async read on the socket.
+ // we aquire a reader lock here to ensure that no other thread
+ // is trying to set shutdownFlag and close the socket.
+ rwLock.AcquireReaderLock(-1);
+
+ if (!shutdownFlag)
+ {
+ // increment the count of pending operations
+ Interlocked.Increment(ref rwOperationCount);
+
+ // allocate a packet buffer
+ //WrappedObject buf = _bufferPool.CheckOut();
+ UDPPacketBuffer buf = new UDPPacketBuffer();
+
+ try
+ {
+ // kick off an async read
+ udpSocket.BeginReceiveFrom(
+ //buf.Instance.Data,
+ buf.Data,
+ 0,
+ UDPPacketBuffer.BUFFER_SIZE,
+ SocketFlags.None,
+ //ref buf.Instance.RemoteEndPoint,
+ ref buf.RemoteEndPoint,
+ new AsyncCallback(AsyncEndReceive),
+ buf);
+ }
+ catch (SocketException se)
+ {
+ // something bad happened
+ SecondLife.LogStatic(
+ "A SocketException occurred in UDPServer.AsyncBeginReceive()",
+ Helpers.LogLevel.Error, se);
+
+ // an error occurred, therefore the operation is void. Decrement the reference count.
+ Interlocked.Decrement(ref rwOperationCount);
+ }
+ }
+
+ // we're done with the socket for now, release the reader lock.
+ rwLock.ReleaseReaderLock();
+ }
+
+ private void AsyncEndReceive(IAsyncResult iar)
+ {
+ // Asynchronous receive operations will complete here through the call
+ // to AsyncBeginReceive
+
+ // aquire a reader lock
+ rwLock.AcquireReaderLock(-1);
+
+ if (!shutdownFlag)
+ {
+ // start another receive - this keeps the server going!
+ AsyncBeginReceive();
+
+ // get the buffer that was created in AsyncBeginReceive
+ // this is the received data
+ //WrappedObject wrappedBuffer = (WrappedObject)iar.AsyncState;
+ //UDPPacketBuffer buffer = wrappedBuffer.Instance;
+ UDPPacketBuffer buffer = (UDPPacketBuffer)iar.AsyncState;
+
+ try
+ {
+ // get the length of data actually read from the socket, store it with the
+ // buffer
+ buffer.DataLength = udpSocket.EndReceiveFrom(iar, ref buffer.RemoteEndPoint);
+
+ // this operation is now complete, decrement the reference count
+ Interlocked.Decrement(ref rwOperationCount);
+
+ // we're done with the socket, release the reader lock
+ rwLock.ReleaseReaderLock();
+
+ // call the abstract method PacketReceived(), passing the buffer that
+ // has just been filled from the socket read.
+ PacketReceived(buffer);
+ }
+ catch (SocketException se)
+ {
+ // something bad happened
+ SecondLife.LogStatic(
+ "A SocketException occurred in UDPServer.AsyncEndReceive()",
+ Helpers.LogLevel.Error, se);
+
+ // an error occurred, therefore the operation is void. Decrement the reference count.
+ Interlocked.Decrement(ref rwOperationCount);
+
+ // we're done with the socket for now, release the reader lock.
+ rwLock.ReleaseReaderLock();
+ }
+
+ //wrappedBuffer.Dispose();
+ }
+ else
+ {
+ // nothing bad happened, but we are done with the operation
+ // decrement the reference count and release the reader lock
+ Interlocked.Decrement(ref rwOperationCount);
+ rwLock.ReleaseReaderLock();
+ }
+ }
+
+ public void AsyncBeginSend(UDPPacketBuffer buf)
+ {
+ rwLock.AcquireReaderLock(-1);
+
+ if (!shutdownFlag)
+ {
+ try
+ {
+ Interlocked.Increment(ref rwOperationCount);
+ udpSocket.BeginSendTo(
+ buf.Data,
+ 0,
+ buf.DataLength,
+ SocketFlags.None,
+ buf.RemoteEndPoint,
+ new AsyncCallback(AsyncEndSend),
+ buf);
+ }
+ catch (SocketException se)
+ {
+ SecondLife.LogStatic(
+ "A SocketException occurred in UDPServer.AsyncBeginSend()",
+ Helpers.LogLevel.Error, se);
+ }
+ }
+
+ rwLock.ReleaseReaderLock();
+ }
+
+ private void AsyncEndSend(IAsyncResult iar)
+ {
+ rwLock.AcquireReaderLock(-1);
+
+ if (!shutdownFlag)
+ {
+ UDPPacketBuffer buffer = (UDPPacketBuffer)iar.AsyncState;
+
+ try
+ {
+ int bytesSent = udpSocket.EndSendTo(iar);
+
+ // note that call to the abstract PacketSent() method - we are passing the number
+ // of bytes sent in a separate parameter, since we can't use buffer.DataLength which
+ // is the number of bytes to send (or bytes received depending upon whether this
+ // buffer was part of a send or a receive).
+ PacketSent(buffer, bytesSent);
+ }
+ catch (SocketException se)
+ {
+ SecondLife.LogStatic(
+ "A SocketException occurred in UDPServer.AsyncEndSend()",
+ Helpers.LogLevel.Error, se);
+ }
+ }
+
+ Interlocked.Decrement(ref rwOperationCount);
+ rwLock.ReleaseReaderLock();
+ }
+ }
+}
diff --git a/libsecondlife/examples/Baker/frmBaker.Designer.cs b/libsecondlife/examples/Baker/frmBaker.Designer.cs
index 7f03c25d..d1f8e958 100644
--- a/libsecondlife/examples/Baker/frmBaker.Designer.cs
+++ b/libsecondlife/examples/Baker/frmBaker.Designer.cs
@@ -29,94 +29,93 @@ namespace Baker
private void InitializeComponent()
{
this.pic1 = new System.Windows.Forms.PictureBox();
- this.cmdLoadPic1 = new System.Windows.Forms.Button();
- this.pic2 = new System.Windows.Forms.PictureBox();
- this.pic3 = new System.Windows.Forms.PictureBox();
- this.cmdLoadPic2 = new System.Windows.Forms.Button();
- this.cmdLoadPic3 = new System.Windows.Forms.Button();
+ this.cmdLoadShirt = new System.Windows.Forms.Button();
+ this.scrollWeight = new System.Windows.Forms.HScrollBar();
+ this.cmdLoadSkin = new System.Windows.Forms.Button();
+ this.cboMask = new System.Windows.Forms.ComboBox();
((System.ComponentModel.ISupportInitialize)(this.pic1)).BeginInit();
- ((System.ComponentModel.ISupportInitialize)(this.pic2)).BeginInit();
- ((System.ComponentModel.ISupportInitialize)(this.pic3)).BeginInit();
this.SuspendLayout();
//
// pic1
//
this.pic1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
- this.pic1.Location = new System.Drawing.Point(12, 12);
+ this.pic1.Location = new System.Drawing.Point(12, 41);
this.pic1.Name = "pic1";
- this.pic1.Size = new System.Drawing.Size(256, 256);
+ this.pic1.Size = new System.Drawing.Size(512, 483);
this.pic1.TabIndex = 0;
this.pic1.TabStop = false;
- this.pic1.AutoSize = true;
//
- // cmdLoadPic1
+ // cmdLoadShirt
//
- this.cmdLoadPic1.Location = new System.Drawing.Point(193, 274);
- this.cmdLoadPic1.Name = "cmdLoadPic1";
- this.cmdLoadPic1.Size = new System.Drawing.Size(75, 23);
- this.cmdLoadPic1.TabIndex = 1;
- this.cmdLoadPic1.Text = "Load";
- this.cmdLoadPic1.UseVisualStyleBackColor = true;
- this.cmdLoadPic1.Click += new System.EventHandler(this.cmdLoadPic_Click);
+ this.cmdLoadShirt.Location = new System.Drawing.Point(449, 530);
+ this.cmdLoadShirt.Name = "cmdLoadShirt";
+ this.cmdLoadShirt.Size = new System.Drawing.Size(75, 23);
+ this.cmdLoadShirt.TabIndex = 1;
+ this.cmdLoadShirt.Text = "Load Shirt";
+ this.cmdLoadShirt.UseVisualStyleBackColor = true;
//
- // pic2
+ // scrollWeight
//
- this.pic2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
- this.pic2.Location = new System.Drawing.Point(274, 12);
- this.pic2.Name = "pic2";
- this.pic2.Size = new System.Drawing.Size(256, 256);
- this.pic2.TabIndex = 2;
- this.pic2.TabStop = false;
- this.pic1.AutoSize = true;
+ this.scrollWeight.Location = new System.Drawing.Point(12, 530);
+ this.scrollWeight.Maximum = 255;
+ this.scrollWeight.Name = "scrollWeight";
+ this.scrollWeight.Size = new System.Drawing.Size(345, 23);
+ this.scrollWeight.TabIndex = 0;
+ this.scrollWeight.Scroll += new System.Windows.Forms.ScrollEventHandler(this.scrollWeight_Scroll);
//
- // pic3
+ // cmdLoadSkin
//
- this.pic3.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
- this.pic3.Location = new System.Drawing.Point(536, 12);
- this.pic3.Name = "pic3";
- this.pic3.Size = new System.Drawing.Size(256, 256);
- this.pic3.TabIndex = 3;
- this.pic3.TabStop = false;
- this.pic1.AutoSize = true;
+ this.cmdLoadSkin.Location = new System.Drawing.Point(366, 530);
+ this.cmdLoadSkin.Name = "cmdLoadSkin";
+ this.cmdLoadSkin.Size = new System.Drawing.Size(75, 23);
+ this.cmdLoadSkin.TabIndex = 2;
+ this.cmdLoadSkin.Text = "Load Skin";
+ this.cmdLoadSkin.UseVisualStyleBackColor = true;
+ this.cmdLoadSkin.Click += new System.EventHandler(this.cmdLoadSkin_Click);
//
- // cmdLoadPic2
+ // cboMask
//
- this.cmdLoadPic2.Location = new System.Drawing.Point(455, 274);
- this.cmdLoadPic2.Name = "cmdLoadPic2";
- this.cmdLoadPic2.Size = new System.Drawing.Size(75, 23);
- this.cmdLoadPic2.TabIndex = 4;
- this.cmdLoadPic2.Text = "Load";
- this.cmdLoadPic2.UseVisualStyleBackColor = true;
- this.cmdLoadPic2.Click += new System.EventHandler(this.cmdLoadPic_Click);
- //
- // cmdLoadPic3
- //
- this.cmdLoadPic3.Location = new System.Drawing.Point(717, 274);
- this.cmdLoadPic3.Name = "cmdLoadPic3";
- this.cmdLoadPic3.Size = new System.Drawing.Size(75, 23);
- this.cmdLoadPic3.TabIndex = 5;
- this.cmdLoadPic3.Text = "Load";
- this.cmdLoadPic3.UseVisualStyleBackColor = true;
- this.cmdLoadPic3.Click += new System.EventHandler(this.cmdLoadPic_Click);
+ this.cboMask.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cboMask.FormattingEnabled = true;
+ this.cboMask.Items.AddRange(new object[] {
+ "glove_length_alpha",
+ "gloves_fingers_alpha",
+ "jacket_length_lower_alpha",
+ "jacket_length_upper_alpha",
+ "jacket_open_lower_alpha",
+ "jacket_open_upper_alpha",
+ "pants_length_alpha",
+ "pants_waist_alpha",
+ "shirt_bottom_alpha",
+ "shirt_collar_alpha",
+ "shirt_collar_back_alpha",
+ "shirt_sleeve_alpha",
+ "shoe_height_alpha",
+ "skirt_length_alpha",
+ "skirt_slit_front_alpha",
+ "skirt_slit_left_alpha",
+ "skirt_slit_right_alpha"});
+ this.cboMask.Location = new System.Drawing.Point(358, 12);
+ this.cboMask.Name = "cboMask";
+ this.cboMask.Size = new System.Drawing.Size(166, 21);
+ this.cboMask.TabIndex = 3;
+ this.cboMask.SelectedIndexChanged += new System.EventHandler(this.cboMask_SelectedIndexChanged);
//
// frmBaker
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(806, 498);
- this.Controls.Add(this.cmdLoadPic3);
- this.Controls.Add(this.cmdLoadPic2);
- this.Controls.Add(this.pic3);
- this.Controls.Add(this.pic2);
- this.Controls.Add(this.cmdLoadPic1);
+ this.ClientSize = new System.Drawing.Size(538, 563);
+ this.Controls.Add(this.cboMask);
+ this.Controls.Add(this.cmdLoadSkin);
+ this.Controls.Add(this.scrollWeight);
+ this.Controls.Add(this.cmdLoadShirt);
this.Controls.Add(this.pic1);
this.Name = "frmBaker";
this.Text = "Baker";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmBaker_FormClosing);
this.Load += new System.EventHandler(this.frmBaker_Load);
((System.ComponentModel.ISupportInitialize)(this.pic1)).EndInit();
- ((System.ComponentModel.ISupportInitialize)(this.pic2)).EndInit();
- ((System.ComponentModel.ISupportInitialize)(this.pic3)).EndInit();
this.ResumeLayout(false);
}
@@ -124,11 +123,10 @@ namespace Baker
#endregion
private System.Windows.Forms.PictureBox pic1;
- private System.Windows.Forms.Button cmdLoadPic1;
- private System.Windows.Forms.PictureBox pic2;
- private System.Windows.Forms.PictureBox pic3;
- private System.Windows.Forms.Button cmdLoadPic2;
- private System.Windows.Forms.Button cmdLoadPic3;
+ private System.Windows.Forms.HScrollBar scrollWeight;
+ private System.Windows.Forms.Button cmdLoadShirt;
+ private System.Windows.Forms.Button cmdLoadSkin;
+ private System.Windows.Forms.ComboBox cboMask;
}
}
diff --git a/libsecondlife/examples/Baker/frmBaker.cs b/libsecondlife/examples/Baker/frmBaker.cs
index 117799e9..678869b7 100644
--- a/libsecondlife/examples/Baker/frmBaker.cs
+++ b/libsecondlife/examples/Baker/frmBaker.cs
@@ -12,6 +12,8 @@ namespace Baker
{
public partial class frmBaker : Form
{
+ Bitmap AlphaMask;
+
public frmBaker()
{
InitializeComponent();
@@ -19,59 +21,65 @@ namespace Baker
private void frmBaker_Load(object sender, EventArgs e)
{
- Stream stream = libsecondlife.Helpers.GetResourceStream("shirt_sleeve_alpha.tga");
-
- if (stream != null)
- {
- Bitmap alphaMask = OpenJPEGNet.LoadTGAClass.LoadTGA(stream);
- pic1.Image = Oven.ModifyAlphaMask(alphaMask, 245, 0.0f);
- }
- else
- {
- ;
- }
+ cboMask.SelectedIndex = 0;
+ DisplayResource(cboMask.Text);
+ }
+
+ private void DisplayResource(string resource)
+ {
+ Stream stream = libsecondlife.Helpers.GetResourceStream(resource + ".tga");
+
+ if (stream != null)
+ {
+ AlphaMask = OpenJPEGNet.LoadTGAClass.LoadTGA(stream);
+ pic1.Image = Oven.ModifyAlphaMask(AlphaMask, (byte)scrollWeight.Value, 0.0f);
+ }
+ else
+ {
+ MessageBox.Show("Failed to load embedded resource \"" + resource + "\"", "Baker",
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+
+ private void scrollWeight_Scroll(object sender, ScrollEventArgs e)
+ {
+ pic1.Image = Oven.ModifyAlphaMask(AlphaMask, (byte)scrollWeight.Value, 0.0f);
}
private void frmBaker_FormClosing(object sender, FormClosingEventArgs e)
{
}
- private void cmdLoadPic_Click(object sender, EventArgs e)
+ private void cmdLoadSkin_Click(object sender, EventArgs e)
{
- Button caller = (Button)sender;
- PictureBox pic = null;
- switch (caller.Name)
- {
- case "cmdLoadPic1":
- pic = pic1;
- break;
- case "cmdLoadPic2":
- pic = pic2;
- break;
- case "cmdLoadPic3":
- pic = pic3;
- break;
- }
+ }
- if (pic != null)
- {
- OpenFileDialog dialog = new OpenFileDialog();
- dialog.Filter = "JPEG2000 (*.jp2,*.j2c,*.j2k)|";
- if (dialog.ShowDialog() == DialogResult.OK)
- {
- try
- {
- byte[] j2kdata = File.ReadAllBytes(dialog.FileName);
- Image image = OpenJPEGNet.OpenJPEG.DecodeToImage(j2kdata);
- pic.Image = image;
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message);
- }
- }
- }
+ private void cmdLoadShirt_Click(object sender, EventArgs e)
+ {
+ OpenFileDialog dialog = new OpenFileDialog();
+
+
+
+ //dialog.Filter = "JPEG2000 (*.jp2,*.j2c,*.j2k)|";
+ //if (dialog.ShowDialog() == DialogResult.OK)
+ //{
+ // try
+ // {
+ // byte[] j2kdata = File.ReadAllBytes(dialog.FileName);
+ // Image image = OpenJPEGNet.OpenJPEG.DecodeToImage(j2kdata);
+ // pic1.Image = image;
+ // }
+ // catch (Exception ex)
+ // {
+ // MessageBox.Show(ex.Message);
+ // }
+ //}
+ }
+
+ private void cboMask_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ DisplayResource(cboMask.Text);
}
}
}
diff --git a/libsecondlife/examples/IA_SimpleInventory/IA_SimpleInventory.cs b/libsecondlife/examples/IA_SimpleInventory/IA_SimpleInventory.cs
deleted file mode 100644
index cce63417..00000000
--- a/libsecondlife/examples/IA_SimpleInventory/IA_SimpleInventory.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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 System.Collections.Generic;
-using System.Threading;
-
-using libsecondlife;
-using libsecondlife.InventorySystem;
-
-namespace IA_SimpleInventory
-{
- ///
- /// A simple base application for building console applications that access SL Inventory
- ///
- public class SimpleInventory
- {
- private SecondLife client;
- private ManualResetEvent ConnectedSignal = new ManualResetEvent(false);
-
- public static void Main(string[] args)
- {
-
- if (args.Length < 3)
- {
- Console.WriteLine("Usage: SimpleInventory [loginfirstname] [loginlastname] [password]");
- return;
- }
-
- SimpleInventory simple = new SimpleInventory();
- if (simple.Connect(args[0], args[1], args[2]))
- {
- if (simple.ConnectedSignal.WaitOne(TimeSpan.FromMinutes(1), false))
- {
- simple.doStuff();
- simple.Disconnect();
- }
- }
- }
-
- protected SimpleInventory()
- {
- try
- {
- client = new SecondLife();
- client.Network.OnConnected += new NetworkManager.ConnectedCallback(Network_OnConnected);
- }
- catch (Exception e)
- {
- // Error initializing the client
- Console.WriteLine();
- Console.WriteLine(e.ToString());
- }
- }
-
- void Network_OnConnected(object sender)
- {
- ConnectedSignal.Set();
- }
-
- protected bool Connect(string FirstName, string LastName, string Password)
- {
- Console.WriteLine("Attempting to connect and login to SecondLife.");
-
- // Setup Login to Second Life
- Dictionary loginReply = new Dictionary();
-
- // Login
- if (!client.Network.Login(FirstName, LastName, Password, "IA_SimpleInventory", "static.sprocket@gmail.com"))
- {
- // Login failed
- Console.WriteLine("Error logging in: " + client.Network.LoginMessage);
- return false;
- }
-
- // Login was successful
- Console.WriteLine("Login was successful.");
- Console.WriteLine("AgentID: " + client.Network.AgentID);
- Console.WriteLine("SessionID: " + client.Network.SessionID);
-
- return true;
- }
-
- protected void Disconnect()
- {
- // Logout of Second Life
- Console.WriteLine("Request logout");
- client.Network.Logout();
- }
-
- protected void doStuff()
- {
- Console.WriteLine("Broken until someone fixes me");
-
- // and request an inventory download
- //Console.WriteLine("Downloading Inventory.");
- //client.Inventory.DownloadInventory();
-
-
- //Console.WriteLine("Dumping a copy of " + client.Self.FirstName + "'s inventory to the console.");
- //Console.WriteLine();
-
- //InventoryFolder root = client.Inventory.GetRootFolder();
-
- //if (root != null)
- //{
- // Console.WriteLine(root.toXML(false));
- //}
- }
- }
-}
diff --git a/libsecondlife/examples/IA_SimpleInventory/IA_SimpleInventory.csproj b/libsecondlife/examples/IA_SimpleInventory/IA_SimpleInventory.csproj
deleted file mode 100644
index 5d7963d3..00000000
--- a/libsecondlife/examples/IA_SimpleInventory/IA_SimpleInventory.csproj
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
- Debug
- AnyCPU
- 8.0.50727
- 2.0
- {E464B963-46E3-4E1A-A36F-9C640C880E68}
- Exe
- Properties
- IA_SimpleInventory
- IA_SimpleInventory
-
-
- true
- full
- false
- ..\..\..\bin\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
-
- {D9CDEDFB-8169-4B03-B57F-0DF638F044EC}
- libsecondlife
-
-
-
-
-
\ No newline at end of file
diff --git a/libsecondlife/examples/IA_SimpleInventory/Properties/AssemblyInfo.cs b/libsecondlife/examples/IA_SimpleInventory/Properties/AssemblyInfo.cs
deleted file mode 100644
index 1e2754cd..00000000
--- a/libsecondlife/examples/IA_SimpleInventory/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("IA_SimpleInventory")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("IA_SimpleInventory")]
-[assembly: AssemblyCopyright("Copyright © 2006")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("a166c7c2-97fd-4bd2-8733-a156db565a4b")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/libsecondlife/examples/TestClient/ClientManager.cs b/libsecondlife/examples/TestClient/ClientManager.cs
index 7fe480c9..c65ea4a9 100644
--- a/libsecondlife/examples/TestClient/ClientManager.cs
+++ b/libsecondlife/examples/TestClient/ClientManager.cs
@@ -5,7 +5,6 @@ using System.Xml;
using System.Threading;
using libsecondlife;
using libsecondlife.Packets;
-using libsecondlife.AssetSystem;
namespace libsecondlife.TestClient
{
diff --git a/libsecondlife/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs b/libsecondlife/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs
index ddbb86cd..657ebc2e 100644
--- a/libsecondlife/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs
+++ b/libsecondlife/examples/TestClient/Commands/Inventory/DeleteFolderCommand.cs
@@ -5,10 +5,7 @@ using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
-
using libsecondlife;
-using libsecondlife.Packets;
-using libsecondlife.InventorySystem;
namespace libsecondlife.TestClient
{
diff --git a/libsecondlife/examples/TestClient/Commands/Inventory/InventoryCommand.cs b/libsecondlife/examples/TestClient/Commands/Inventory/InventoryCommand.cs
index 7b01bb1b..a6b14190 100644
--- a/libsecondlife/examples/TestClient/Commands/Inventory/InventoryCommand.cs
+++ b/libsecondlife/examples/TestClient/Commands/Inventory/InventoryCommand.cs
@@ -5,10 +5,8 @@ using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
-
using libsecondlife;
using libsecondlife.Packets;
-using libsecondlife.InventorySystem;
namespace libsecondlife.TestClient
{
diff --git a/libsecondlife/examples/TestClient/Commands/Inventory/WearCommand.cs b/libsecondlife/examples/TestClient/Commands/Inventory/WearCommand.cs
index dc8d0410..42fc7d68 100644
--- a/libsecondlife/examples/TestClient/Commands/Inventory/WearCommand.cs
+++ b/libsecondlife/examples/TestClient/Commands/Inventory/WearCommand.cs
@@ -5,10 +5,8 @@ using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
-
using libsecondlife;
using libsecondlife.Packets;
-using libsecondlife.InventorySystem;
namespace libsecondlife.TestClient
{
@@ -24,34 +22,22 @@ namespace libsecondlife.TestClient
public override string Execute(string[] args, LLUUID fromAgentID)
{
string target = String.Empty;
- bool oldcode = false;
-
for (int ct = 0; ct < args.Length; ct++)
- {
- if (args[ct] == "oldcode")
- oldcode = true;
- else
- target = target + args[ct] + " ";
- }
+ target = target + args[ct] + " ";
- target = target.TrimEnd();
-
- InventoryFolder folder = Client.Inventory.getFolder(target);
+ //target = target.TrimEnd();
+ //InventoryFolder folder = Client.Inventory.getFolder(target);
- if (folder != null)
- {
- //libsecondlife.Utilities.Appearance.AppearanceManager am = new libsecondlife.Utilities.Appearance.AppearanceManager(Client, new Utilities.Assets.AssetManager(Client));
-
- if (oldcode)
- Client.Appearance.WearOutfit(folder);
- else
- Client.NewAppearanceManager.WearOutfit(folder);
+ //if (folder != null)
+ //{
+ // Client.Appearance.WearOutfit(folder);
+ // return "Outfit " + target + " worn.";
+ //}
- return "Outfit " + target + " worn.";
- }
+ //return "Unable to find: " + target;
- return "Unable to find: " + target;
+ return "Broken, someone please update me";
}
}
}
diff --git a/libsecondlife/examples/TestClient/TestClient.cs b/libsecondlife/examples/TestClient/TestClient.cs
index d7550c7e..4fdb143d 100644
--- a/libsecondlife/examples/TestClient/TestClient.cs
+++ b/libsecondlife/examples/TestClient/TestClient.cs
@@ -4,7 +4,6 @@ using System.Reflection;
using System.Xml;
using libsecondlife;
using libsecondlife.Packets;
-using libsecondlife.AssetSystem;
namespace libsecondlife.TestClient
{
@@ -66,6 +65,7 @@ namespace libsecondlife.TestClient
Objects.OnNewAvatar += new ObjectManager.NewAvatarCallback(Objects_OnNewAvatar);
Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(Self_OnInstantMessage);
Groups.OnGroupMembers += new GroupManager.GroupMembersCallback(GroupMembersHandler);
+ Inventory.OnInventoryObjectReceived += new InventoryManager.InventoryObjectReceived(Inventory_OnInventoryObjectReceived);
this.OnLogMessage += new LogCallback(TestClient_OnLogMessage);
Network.RegisterCallback(PacketType.AvatarAppearance, new NetworkManager.PacketCallback(AvatarAppearanceHandler));
@@ -282,53 +282,60 @@ namespace libsecondlife.TestClient
lock (Appearances) Appearances[appearance.Sender.ID] = appearance;
}
- private void Self_OnInstantMessage(LLUUID fromAgentID, string fromAgentName, LLUUID toAgentID,
- uint parentEstateID, LLUUID regionID, LLVector3 position, MainAvatar.InstantMessageDialog dialog,
- bool groupIM, LLUUID imSessionID, DateTime timestamp, string message,
- MainAvatar.InstantMessageOnline offline, byte[] binaryBucket)
+ private void Self_OnInstantMessage(LLUUID fromAgentID, string fromAgentName, LLUUID toAgentID,
+ uint parentEstateID, LLUUID regionID, LLVector3 position, MainAvatar.InstantMessageDialog dialog,
+ bool groupIM, LLUUID imSessionID, DateTime timestamp, string message,
+ MainAvatar.InstantMessageOnline offline, byte[] binaryBucket, Simulator simulator)
{
if (MasterKey != LLUUID.Zero)
{
if (fromAgentID != MasterKey)
{
// Received an IM from someone that is not the bot's master, ignore
- Console.WriteLine("" + fromAgentName + " (not master): " + message + "@" + regionID.ToString() + ":" + position.ToString() );
+ Console.WriteLine(" {1} (not master): {2} (@{3}:{4})", dialog, fromAgentName, message,
+ regionID, position);
return;
}
}
- else
+ else if (GroupMembers != null && !GroupMembers.ContainsKey(fromAgentID))
{
- if (GroupMembers != null && !GroupMembers.ContainsKey(fromAgentID))
- {
- // Received an IM from someone outside the bot's group, ignore
- Console.WriteLine("" + fromAgentName + " (not in group): " + message + "@" + regionID.ToString() + ":" + position.ToString());
- return;
- }
+ // Received an IM from someone outside the bot's group, ignore
+ Console.WriteLine(" {1} (not in group): {2} (@{3}:{4})", dialog, fromAgentName,
+ message, regionID, position);
+ return;
}
- Console.WriteLine("" + fromAgentName + ": " + message);
+ // Received an IM from someone that is authenticated
+ Console.WriteLine(" {1}: {2} (@{3}:{4})", dialog, fromAgentName, message, regionID, position);
if (dialog == MainAvatar.InstantMessageDialog.RequestTeleport)
{
Console.WriteLine("Accepting teleport lure.");
Self.TeleportLureRespond(fromAgentID, true);
}
- else
+ else if (
+ dialog == MainAvatar.InstantMessageDialog.MessageFromAgent ||
+ dialog == MainAvatar.InstantMessageDialog.MessageFromObject)
{
- if (dialog == MainAvatar.InstantMessageDialog.InventoryOffered)
- {
- Console.WriteLine("Accepting inventory offer.");
-
- Self.InstantMessage(Self.FirstName + " " + Self.LastName, fromAgentID, String.Empty,
- imSessionID, MainAvatar.InstantMessageDialog.InventoryAccepted,
- MainAvatar.InstantMessageOnline.Offline, Self.Position, LLUUID.Zero,
- Self.InventoryRootFolderUUID.GetBytes());
- }
- else
- {
- DoCommand(message, fromAgentID, imSessionID);
- }
+ DoCommand(message, fromAgentID, imSessionID);
}
}
+
+ private bool Inventory_OnInventoryObjectReceived(LLUUID fromAgentID, string fromAgentName,
+ uint parentEstateID, LLUUID regionID, LLVector3 position, DateTime timestamp, AssetType type,
+ LLUUID objectID, bool fromTask)
+ {
+ if (MasterKey != LLUUID.Zero)
+ {
+ if (fromAgentID != MasterKey)
+ return false;
+ }
+ else if (GroupMembers != null && !GroupMembers.ContainsKey(fromAgentID))
+ {
+ return false;
+ }
+
+ return true;
+ }
}
}
diff --git a/libsecondlife/examples/groupmanager/frmGroupInfo.cs b/libsecondlife/examples/groupmanager/frmGroupInfo.cs
index eaf11a7e..acaa2641 100644
--- a/libsecondlife/examples/groupmanager/frmGroupInfo.cs
+++ b/libsecondlife/examples/groupmanager/frmGroupInfo.cs
@@ -23,7 +23,7 @@ namespace groupmanager
GroupManager.GroupMembersCallback GroupMembersCallback;
GroupManager.GroupTitlesCallback GroupTitlesCallback;
AvatarManager.AvatarNamesCallback AvatarNamesCallback;
- AssetManager Assets;
+ AssetManager.ImageReceivedCallback ImageReceivedCallback;
public frmGroupInfo(Group group, SecondLife client)
{
@@ -39,13 +39,13 @@ namespace groupmanager
GroupMembersCallback = new GroupManager.GroupMembersCallback(GroupMembersHandler);
GroupTitlesCallback = new GroupManager.GroupTitlesCallback(GroupTitlesHandler);
AvatarNamesCallback = new AvatarManager.AvatarNamesCallback(AvatarNamesHandler);
+ ImageReceivedCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived);
Group = group;
Client = client;
- Assets = new AssetManager(Client);
- Assets.OnImageReceived += new AssetManager.ImageReceivedCallback(Assets_OnImageReceived);
-
+
// Register the callbacks for this form
+ Client.Assets.OnImageReceived += ImageReceivedCallback;
Client.Groups.OnGroupProfile += GroupProfileCallback;
Client.Groups.OnGroupMembers += GroupMembersCallback;
Client.Groups.OnGroupTitles += GroupTitlesCallback;
@@ -60,6 +60,7 @@ namespace groupmanager
~frmGroupInfo()
{
// Unregister the callbacks for this form
+ Client.Assets.OnImageReceived -= ImageReceivedCallback;
Client.Groups.OnGroupProfile -= GroupProfileCallback;
Client.Groups.OnGroupMembers -= GroupMembersCallback;
Client.Groups.OnGroupTitles -= GroupTitlesCallback;
@@ -70,20 +71,17 @@ namespace groupmanager
{
Profile = profile;
- Invoke(new MethodInvoker(UpdateProfile));
-
if (Group.InsigniaID != null && Group.InsigniaID != LLUUID.Zero)
- {
- Assets.RequestImage(Group.InsigniaID, ImageType.Normal, 113000.0f, 0);
- }
+ Client.Assets.RequestImage(Group.InsigniaID, ImageType.Normal, 113000.0f, 0);
+
+ if (this.InvokeRequired)
+ this.BeginInvoke(new MethodInvoker(UpdateProfile));
}
void Assets_OnImageReceived(ImageDownload image)
{
if (image.Success)
- {
picInsignia.Image = OpenJPEGNet.OpenJPEG.DecodeToImage(image.AssetData);
- }
}
private void UpdateProfile()
diff --git a/libsecondlife/examples/groupmanager/frmGroupManager.cs b/libsecondlife/examples/groupmanager/frmGroupManager.cs
index f9a0a240..66f8ad0a 100644
--- a/libsecondlife/examples/groupmanager/frmGroupManager.cs
+++ b/libsecondlife/examples/groupmanager/frmGroupManager.cs
@@ -67,7 +67,7 @@ namespace groupmanager
txtFirstName.Enabled = txtLastName.Enabled = txtPassword.Enabled = false;
if (Client.Network.Login(txtFirstName.Text, txtLastName.Text, txtPassword.Text, "GroupManager",
- "jhurliman@wsu.edu"))
+ "jhurliman@metaverseindustries.com"))
{
groupBox.Enabled = true;
diff --git a/libsecondlife/examples/name2key/name2key.cs b/libsecondlife/examples/name2key/name2key.cs
index a0afd3e1..1b8dc846 100644
--- a/libsecondlife/examples/name2key/name2key.cs
+++ b/libsecondlife/examples/name2key/name2key.cs
@@ -78,7 +78,7 @@ namespace name2key
// Setup the callback
client.Directory.OnDirPeopleReply += new DirectoryManager.DirPeopleReplyCallback(DirQueryHandler);
- if (!client.Network.Login(args[0], args[1], args[2], "name2key", "jhurliman@wsu.edu"))
+ if (!client.Network.Login(args[0], args[1], args[2], "name2key", "jhurliman@metaverseindustries.com"))
{
// Login failed
Console.WriteLine("ERROR: " + client.Network.LoginMessage);
diff --git a/libsecondlife/examples/slaccountant/frmSLAccountant.cs b/libsecondlife/examples/slaccountant/frmSLAccountant.cs
index 18a2a2a9..8ad6a3ce 100644
--- a/libsecondlife/examples/slaccountant/frmSLAccountant.cs
+++ b/libsecondlife/examples/slaccountant/frmSLAccountant.cs
@@ -409,7 +409,7 @@ namespace SLAccountant
txtFirstName.Enabled = txtLastName.Enabled = txtPassword.Enabled = false;
if (client.Network.Login(txtFirstName.Text, txtLastName.Text, txtPassword.Text,
- "accountant", "jhurliman@wsu.edu"))
+ "accountant", "jhurliman@metaverseindustries.com"))
{
Random rand = new Random();
diff --git a/libsecondlife/libsecondlife.csproj b/libsecondlife/libsecondlife.csproj
index 65187c06..0ad4a81f 100644
--- a/libsecondlife/libsecondlife.csproj
+++ b/libsecondlife/libsecondlife.csproj
@@ -92,21 +92,6 @@
Code
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Code
@@ -117,10 +102,8 @@
-
-
-
-
+
+
@@ -134,14 +117,6 @@
-
-
-
-
-
-
-
-
Code
@@ -149,6 +124,7 @@
Code
+
Code
@@ -174,12 +150,64 @@
Code
+
+
Code
+
+
+
+ {D0DCFDCB-71FA-4343-A8D1-24D4665A94A4}
+ openjpegnet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -200,55 +228,6 @@
-
-
- {D0DCFDCB-71FA-4343-A8D1-24D4665A94A4}
- openjpegnet
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/libsecondlife/libsecondlife.mdp b/libsecondlife/libsecondlife.mdp
deleted file mode 100644
index 4319edbd..00000000
--- a/libsecondlife/libsecondlife.mdp
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/libsecondlife/libsecondlife.mds b/libsecondlife/libsecondlife.mds
deleted file mode 100644
index 20b4cd37..00000000
--- a/libsecondlife/libsecondlife.mds
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/openjpegnet/openjpeg.dll b/openjpegnet/openjpeg.dll
deleted file mode 100644
index 57e27946..00000000
Binary files a/openjpegnet/openjpeg.dll and /dev/null differ