diff --git a/bin/openjpeg-libsl.dll b/bin/openjpeg-libsl.dll index 2076bc58..3134711f 100644 Binary files a/bin/openjpeg-libsl.dll and b/bin/openjpeg-libsl.dll differ diff --git a/importprimscript/importprimscript.cs b/importprimscript/importprimscript.cs new file mode 100644 index 00000000..d78be734 --- /dev/null +++ b/importprimscript/importprimscript.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.IO; +using System.Drawing; +using libsecondlife; + +namespace importprimscript +{ + class Sculpt + { + public string Name; + public string TextureFile; + public LLUUID TextureID; + public string SculptFile; + public LLUUID SculptID; + public LLVector3 Scale; + public LLVector3 Offset; + } + + class importprimscript + { + static SecondLife Client; + static AssetManager Assets; + static AssetUpload CurrentUpload = null; + static AutoResetEvent UploadEvent = new AutoResetEvent(false); + static Sculpt CurrentSculpt = null; + static AutoResetEvent RezzedEvent = new AutoResetEvent(false); + static LLVector3 RootPosition = LLVector3.Zero; + static List RezzedPrims = new List(); + + static void Main(string[] args) + { + if (args.Length != 8) + { + Console.WriteLine("Usage: importprimscript.exe [firstname] [lastname] [password] " + + "[Simulator] [x] [y] [z] [input.primscript]" + + Environment.NewLine + "Example: importprimscript.exe My Bot password " + + "Hooper 128 128 40 maya-export" + Path.DirectorySeparatorChar + "ant.primscript"); + Environment.Exit(-1); + } + + // Strip quotes from any arguments + for (int i = 0; i < args.Length; i++) + args[i] = args[i].Trim(new char[] { '"' }); + + // Parse the primscript file + string error; + List sculpties = ParsePrimscript(args[7], out error); + + // Check for parsing errors + if (error != String.Empty) + { + Console.WriteLine("An error was encountered reading the input file: " + error); + Environment.Exit(-2); + } + else if (sculpties.Count == 0) + { + Console.WriteLine("No primitives were read from the input file"); + Environment.Exit(-3); + } + + // Initialize libsecondlife + Client = new SecondLife(); + Assets = new AssetManager(Client); + + // Add callback handlers for asset uploads finishing. new prims spotted, and logging + Assets.OnAssetUploaded += new AssetManager.AssetUploadedCallback(Assets_OnAssetUploaded); + Client.Objects.OnNewPrim += new ObjectManager.NewPrimCallback(Objects_OnNewPrim); + Client.OnLogMessage += new SecondLife.LogCallback(Client_OnLogMessage); + + // Optimize the connection for our purposes + Client.Self.Status.Camera.Far = 32.0f; + Client.Settings.MULTIPLE_SIMS = false; + Client.Settings.SEND_AGENT_UPDATES = true; + Client.Settings.ENABLE_CAPS = false; + Client.Settings.DEBUG = false; + Client.Settings.ENABLE_SIMSTATS = false; + Client.Settings.ALWAYS_REQUEST_OBJECTS = true; + Client.Settings.ALWAYS_DECODE_OBJECTS = true; + Client.Throttle.Land = 0; + Client.Throttle.Wind = 0; + Client.Throttle.Cloud = 0; + // Not sure if Asset or Texture will help with uploads, but it won't hurt + Client.Throttle.Asset = 220000.0f; + Client.Throttle.Texture = 446000.0f; + Client.Throttle.Task = 446000.0f; + + int x = Int32.Parse(args[4]); + int y = Int32.Parse(args[5]); + int z = Int32.Parse(args[6]); + string start = NetworkManager.StartLocation(args[3], x, y, z); + + // Attempt to login + if (!Client.Network.Login(args[0], args[1], args[2], "importprimscript 1.0.0", start, + "John Hurliman ")) + { + Console.WriteLine("Login failed: " + Client.Network.LoginMessage); + Environment.Exit(-4); + } + + // Wait a moment for the initial flood of packets to die down + Console.WriteLine("Login succeeded, pausing for a moment..."); + System.Threading.Thread.Sleep(1000 * 10); + + // Set the root position for the import + RootPosition = Client.Self.Position; + RootPosition.Z += 3.0f; + + for (int i = 0; i < sculpties.Count; i++) + { + // Upload the sculpt map and texture + sculpties[i].SculptID = UploadImage(sculpties[i].SculptFile, true); + sculpties[i].TextureID = UploadImage(sculpties[i].TextureFile, false); + + // Check for failed uploads + if (sculpties[i].SculptID == LLUUID.Zero) + { + Client.Log("Sculpt map " + sculpties[i].SculptFile + + " failed to upload, skipping " + sculpties[i].Name, Helpers.LogLevel.Warning); + continue; + } + else if (sculpties[i].TextureID == LLUUID.Zero) + { + Client.Log("Texture " + sculpties[i].TextureFile + + " failed to upload, skipping " + sculpties[i].Name, Helpers.LogLevel.Warning); + continue; + } + + LLObject.ObjectData prim = new LLObject.ObjectData(); + prim.PCode = ObjectManager.PCode.Prim; + prim.Material = LLObject.MaterialType.Wood; + prim.PathScaleY = 0.5f; + prim.PathCurve = 32; + + // Rez this prim + CurrentSculpt = sculpties[i]; + Client.Objects.AddPrim(Client.Network.CurrentSim, prim, LLUUID.Zero, + RootPosition + CurrentSculpt.Offset, CurrentSculpt.Scale, + LLQuaternion.Identity); + + // Wait for the prim to rez and the properties be set for it + if (!RezzedEvent.WaitOne(1000 * 10, false)) + { + Console.WriteLine("Timed out waiting for prim " + CurrentSculpt.Name + " to rez, skipping"); + continue; + } + } + + CurrentSculpt = null; + + lock (RezzedPrims) + { + // Set the permissions + Client.Objects.SetPermissions(Client.Network.CurrentSim, RezzedPrims, PermissionWho.All, + PermissionMask.All, true); + + // Link the object together + Client.Objects.LinkPrims(Client.Network.CurrentSim, RezzedPrims); + } + + Console.WriteLine("Rezzed, textured, and linked " + RezzedPrims.Count + " sculpted prims, logging out..."); + + Client.Network.Logout(); + } + + static void Client_OnLogMessage(string message, Helpers.LogLevel level) + { + if (level >= Helpers.LogLevel.Warning) + Console.WriteLine(level + ": " + message); + } + + static LLUUID UploadImage(string filename, bool lossless) + { + byte[] jp2data = null; + + try + { + Bitmap image = (Bitmap)Bitmap.FromFile(filename); + jp2data = OpenJPEGNet.OpenJPEG.EncodeFromImage(image, lossless); + } + catch (Exception ex) + { + Client.Log("Failed to encode image file " + filename + ": " + ex.ToString(), Helpers.LogLevel.Error); + return LLUUID.Zero; + } + + CurrentUpload = null; + Assets.RequestUpload(LLUUID.Random(), AssetType.Texture, jp2data, false, false, true); + + // The textures are small, 60 seconds should be plenty + UploadEvent.WaitOne(1000 * 60, false); + + if (CurrentUpload != null) + { + if (CurrentUpload.Success) + { + // Pay for the upload + Client.Self.GiveMoney(LLUUID.Zero, Client.Settings.UPLOAD_COST, "importprimscript"); + + Console.WriteLine("Finished uploading image " + filename + ", AssetID: " + + CurrentUpload.AssetID.ToStringHyphenated()); + return CurrentUpload.AssetID; + } + else + { + Client.Log("Upload rejected for image file " + filename, Helpers.LogLevel.Error); + return LLUUID.Zero; + } + } + else + { + Client.Log("Failed to upload image file " + filename + ", upload timed out", Helpers.LogLevel.Error); + // TODO: Add a libsecondlife method for aborting a transfer + return LLUUID.Zero; + } + } + + static void Assets_OnAssetUploaded(AssetUpload upload) + { + CurrentUpload = upload; + UploadEvent.Set(); + } + + static void Objects_OnNewPrim(Simulator simulator, Primitive prim, ulong regionHandle, ushort timeDilation) + { + if (CurrentSculpt != null && (prim.Flags & LLObject.ObjectFlags.CreateSelected) != 0 && + !RezzedPrims.Contains(prim.LocalID)) + { + lock (RezzedPrims) RezzedPrims.Add(prim.LocalID); + + Console.WriteLine("Rezzed prim " + CurrentSculpt.Name + ", setting properties"); + + // Deselect the prim + Client.Objects.DeselectObject(Client.Network.CurrentSim, prim.LocalID); + + // Set the prim position + Client.Objects.SetPosition(Client.Network.CurrentSim, prim.LocalID, + RootPosition + CurrentSculpt.Offset); + + // Set the texture + LLObject.TextureEntry textures = new LLObject.TextureEntry(CurrentSculpt.TextureID); + Client.Objects.SetTextures(Client.Network.CurrentSim, prim.LocalID, textures); + + // Turn it in to a sculpted prim + Primitive.SculptData sculpt = new Primitive.SculptData(); + sculpt.SculptTexture = CurrentSculpt.SculptID; + sculpt.Type = Primitive.SculptType.Sphere; + Client.Objects.SetSculpt(Client.Network.CurrentSim, prim.LocalID, sculpt); + + // Set the prim name + if (!String.IsNullOrEmpty(CurrentSculpt.Name)) + Client.Objects.SetName(Client.Network.CurrentSim, prim.LocalID, CurrentSculpt.Name); + + RezzedEvent.Set(); + } + } + + static List ParsePrimscript(string primscriptfile, out string error) + { + string line; + Sculpt current = null; + List sculpties = new List(); + error = String.Empty; + StreamReader primscript = null; + + // Parse a directory out of the primscriptfile string, if one exists + string path = Path.GetDirectoryName(primscriptfile); + if (!String.IsNullOrEmpty(path)) + path += Path.DirectorySeparatorChar; + else + path = String.Empty; + + try + { + primscript = File.OpenText(primscriptfile); + + while ((line = primscript.ReadLine()) != null) + { + string[] words = line.Split(new char[] { ' ' }); + + if (words.Length > 0) + { + if (current != null) + { + switch (words[0]) + { + case "newPrim": + if (current.Scale != LLVector3.Zero && + !String.IsNullOrEmpty(current.SculptFile) && + !String.IsNullOrEmpty(current.TextureFile)) + { + // Add the previous prim to the list as it is now finalized + sculpties.Add(current); + } + + // Start a new prim + current = new Sculpt(); + + break; + case "prim": + // The only useful bit of information here is the prim name + if (words.Length >= 3) + current.Name = words[2]; + break; + case "shape": + // This line has the name of the sculpt texture + if (words.Length >= 3) + current.SculptFile = path + words[2] + ".bmp"; + break; + case "texture": + // This line has the name of the actual texture + if (words.Length >= 3) + current.TextureFile = path + words[2] + ".bmp"; + break; + case "transform": + // Get some primitive params + if (words.Length >= 9) + { + float x, y, z; + x = Single.Parse(words[2]); + y = Single.Parse(words[3]); + z = Single.Parse(words[4]); + current.Scale = new LLVector3(x, y, z); + + x = Single.Parse(words[6]); + y = Single.Parse(words[7]); + z = Single.Parse(words[8]); + current.Offset = new LLVector3(x, y, z); + } + break; + } + } + else if (words[0] == "newPrim") + { + // Start a new prim + current = new Sculpt(); + } + } + } + + // Add the final prim + if (current != null && current.Scale != LLVector3.Zero && + !String.IsNullOrEmpty(current.SculptFile) && + !String.IsNullOrEmpty(current.TextureFile)) + { + // Add the previous prim to the list as it is now finalized + sculpties.Add(current); + } + } + catch (Exception ex) + { + error = ex.ToString(); + } + finally + { + if (primscript != null) + primscript.Close(); + } + + return sculpties; + } + } +} diff --git a/libsecondlife/examples/IA_ImageTool/IA_ImageTool.csproj b/importprimscript/importprimscript.csproj similarity index 77% rename from libsecondlife/examples/IA_ImageTool/IA_ImageTool.csproj rename to importprimscript/importprimscript.csproj index f7dcdba4..e6693827 100644 --- a/libsecondlife/examples/IA_ImageTool/IA_ImageTool.csproj +++ b/importprimscript/importprimscript.csproj @@ -1,59 +1,60 @@ - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {8D2E5240-2247-42F5-AAAC-CF0CCCEE349A} - Exe - Properties - IA_ImageTool - ImageTool - IA_ImageTool.ImageTool - - - true - full - false - ..\..\..\bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - {D0DCFDCB-71FA-4343-A8D1-24D4665A94A4} - openjpegnet - - - {D9CDEDFB-8169-4B03-B57F-0DF638F044EC} - libsecondlife - - - - + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {32A7AA59-5129-4446-A6DC-2F581ED1A25C} + Exe + Properties + importprimscript + importprimscript + + + true + full + false + ..\bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + {D9CDEDFB-8169-4B03-B57F-0DF638F044EC} + libsecondlife + + + {D0DCFDCB-71FA-4343-A8D1-24D4665A94A4} + openjpegnet + + + + + + + \ No newline at end of file diff --git a/libsecondlife/libsecondlife.Utilities/Appearance.cs b/libsecondlife/AppearanceManager.cs similarity index 87% rename from libsecondlife/libsecondlife.Utilities/Appearance.cs rename to libsecondlife/AppearanceManager.cs index e775f168..a3c2b867 100644 --- a/libsecondlife/libsecondlife.Utilities/Appearance.cs +++ b/libsecondlife/AppearanceManager.cs @@ -1,1254 +1,1345 @@ -/* - * 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 libsecondlife; -using libsecondlife.Utilities.Assets; -using libsecondlife.Packets; - -namespace libsecondlife.Utilities.Appearance -{ - /// - /// - /// - 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 Helpers.PermissionType BasePermissions; - public Helpers.PermissionType EveryonePermissions; - public Helpers.PermissionType OwnerPermissions; - public Helpers.PermissionType NextOwnerPermissions; - public Helpers.PermissionType GroupPermissions; - 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]; - - // 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 - BasePermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); - } - else if (fields[0] == "base_mask") - { - BasePermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); - } - else if (fields[0] == "owner_mask") - { - OwnerPermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); - } - else if (fields[0] == "group_mask") - { - GroupPermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); - } - else if (fields[0] == "everyone_mask") - { - EveryonePermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); - } - else if (fields[0] == "next_owner_mask") - { - NextOwnerPermissions = (Helpers.PermissionType)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 - { - int id = 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)BasePermissions)); data.Append("\n"); - data.Append("\t\towner_mask\t"); data.Append(Helpers.UIntToHexString((uint)OwnerPermissions)); data.Append("\n"); - data.Append("\t\tgroup_mask\t"); data.Append(Helpers.UIntToHexString((uint)GroupPermissions)); data.Append("\n"); - data.Append("\t\teveryone_mask\t"); data.Append(Helpers.UIntToHexString((uint)EveryonePermissions)); data.Append("\n"); - data.Append("\t\tnext_owner_mask\t"); data.Append(Helpers.UIntToHexString((uint)NextOwnerPermissions)); 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(); - } - } - - /// - /// - /// - public struct WearableData - { - public Wearable Wearable; - public LLUUID AssetID; - public LLUUID ItemID; - } - - /// - /// - /// - public class AppearanceManager - { - /// - /// - /// - public enum TextureIndex - { - Unknown = -1, - HeadBodypaint = 0, - UpperShirt, - LowerPants, - EyesIris, - Hair, - UpperBodypaint, - LowerBodypaint, - LowerShoes, - HeadBaked, - UpperBaked, - LowerBaked, - EyesBaked, - LowerSocks, - UpperJacket, - LowerJacket, - 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[19]; - // 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 int CacheCheckSerialNum = 0; - private uint SetAppearanceSerialNum = 0; - 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, libsecondlife.Utilities.Assets.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(); - } - - /// - /// Build hashes out of the texture assetIDs for each baking layer to - /// ask the simulator whether it has cached copies of each baked layer - /// - public void 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 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(); - - // Send a list of what we are currently wearing - SendAgentWearables(); - - 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 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.Length]; - - lock (Wearables) - { - // Only for debugging output - int count = 0; - - // Build the visual param array - for (int i = 0; i < VisualParams.Params.Length; i++) - { - bool found = false; - set.VisualParam[i] = new AgentSetAppearancePacket.VisualParamBlock(); - - // Try and find this value in our collection of downloaded wearables - foreach (WearableData data in Wearables.Values) - { - if (data.Wearable.Params.ContainsKey(i)) - { - set.VisualParam[i].ParamValue = Helpers.FloatToByte(data.Wearable.Params[i], - VisualParams.Params[i].MinValue, VisualParams.Params[i].MaxValue); - found = true; - count++; - break; - } - } - - // Use a default value if we don't have one set for it - if (!found) - { - set.VisualParam[i].ParamValue = Helpers.FloatToByte(VisualParams.Params[i].DefaultValue, - VisualParams.Params[i].MinValue, VisualParams.Params[i].MaxValue); - } - } - - Client.DebugLog("Sending " + 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[25].ParamValue, VisualParams.Params[25].MinValue, - VisualParams.Params[25].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 SendAgentWearables() - { - Client.DebugLog("Wearables contains " + Wearables.Count + " entries"); - - 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 BakedIndexToAgentTextureIndex(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 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) - { - KeyValuePair download = - new KeyValuePair(data.AssetID, assetType); - DownloadQueue.Enqueue(download); - } - } - } - - if (DownloadQueue.Count > 0) - { - KeyValuePair download = DownloadQueue.Dequeue(); - Assets.RequestAsset(download.Key, download.Value, true); - } - - // Don't download wearables twice in a row - DownloadWearables = false; - } - - 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) - { - AgentCachedTextureResponsePacket response = (AgentCachedTextureResponsePacket)packet; - Dictionary paramValues = new Dictionary(VisualParams.Params.Length); - - // Build a dictionary of appearance parameter indices and values from the wearables - for (int i = 0; i < VisualParams.Params.Length; i++) - { - bool found = false; - - // Try and find this value in our collection of downloaded wearables - foreach (WearableData data in Wearables.Values) - { - if (data.Wearable.Params.ContainsKey(i)) - { - paramValues.Add(i,data.Wearable.Params[i]); - found = true; - break; - } - } - - // Use a default value if we don't have one set for it - if (!found) paramValues.Add(i, VisualParams.Params[i].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); - - // Convert the baked index to an AgentTexture index - if (block.TextureID != LLUUID.Zero && host.Length == 0) - { - TextureIndex index = BakedIndexToAgentTextureIndex((BakeType)block.TextureIndex); - AgentTextures[(int)index] = block.TextureID; - } - else - { - BakeType bakeType = (BakeType)block.TextureIndex; - 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.UpperUndershirt); - imageCount += AddImageDownload(TextureIndex.UpperShirt); - imageCount += AddImageDownload(TextureIndex.UpperJacket); - // FIXME: Where are the gloves? - } - 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 (imageCount > 0 && !PendingBakes.ContainsKey(bakeType)) - { - Client.DebugLog("Initializing bake for " + bakeType.ToString()); - - lock (PendingBakes) - PendingBakes.Add(bakeType, new BakeLayer(Client, 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)) - ImageDownloads.Add(image, index); - - return 1; - } - else - { - return 0; - } - } - - private void Assets_OnAssetReceived(AssetDownload asset) - { - 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)) - { - lock (AgentTextures) - { - foreach (KeyValuePair texture in data.Wearable.Textures) - AgentTextures[texture.Key] = texture.Value; - } - - Client.DebugLog("Imported wearable asset " + data.Wearable.Type.ToString()); - } - 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 - KeyValuePair download = DownloadQueue.Dequeue(); - Assets.RequestAsset(download.Key, download.Value, true); - } - else - { - // Everything is downloaded - WearablesDownloadedEvent.Set(); - } - } - - private void Assets_OnImageReceived(ImageDownload image) - { - lock (ImageDownloads) - { - if (ImageDownloads.ContainsKey(image.ID)) - { - TextureIndex index = ImageDownloads[image.ID]; - BakeType type = BakeType.Head; - BakeLayer.BakeOrder order = BakeLayer.BakeOrder.HeadBodypaint; - - Client.DebugLog("Finished downloading texture for " + index.ToString()); - - if (image.Success) - { - // Add this image to a baking layer - switch (index) - { - case TextureIndex.HeadBodypaint: - type = BakeType.Head; - order = BakeLayer.BakeOrder.HeadBodypaint; - break; - case TextureIndex.Hair: - type = BakeType.Head; - order = BakeLayer.BakeOrder.Hair; - break; - case TextureIndex.UpperBodypaint: - type = BakeType.UpperBody; - order = BakeLayer.BakeOrder.UpperBodypaint; - break; - case TextureIndex.UpperUndershirt: - type = BakeType.UpperBody; - order = BakeLayer.BakeOrder.UpperUndershirt; - break; - case TextureIndex.UpperShirt: - type = BakeType.UpperBody; - order = BakeLayer.BakeOrder.UpperShirt; - break; - case TextureIndex.UpperJacket: - type = BakeType.UpperBody; - order = BakeLayer.BakeOrder.UpperJacket; - break; - case TextureIndex.LowerBodypaint: - type = BakeType.LowerBody; - order = BakeLayer.BakeOrder.LowerBodypaint; - break; - case TextureIndex.LowerUnderpants: - type = BakeType.LowerBody; - order = BakeLayer.BakeOrder.LowerUnderpants; - break; - case TextureIndex.LowerSocks: - type = BakeType.LowerBody; - order = BakeLayer.BakeOrder.LowerSocks; - break; - case TextureIndex.LowerShoes: - type = BakeType.LowerBody; - order = BakeLayer.BakeOrder.LowerShoes; - break; - case TextureIndex.LowerPants: - type = BakeType.LowerBody; - order = BakeLayer.BakeOrder.LowerPants; - break; - case TextureIndex.LowerJacket: - type = BakeType.LowerBody; - order = BakeLayer.BakeOrder.LowerJacket; - break; - case TextureIndex.EyesIris: - type = BakeType.Eyes; - order = BakeLayer.BakeOrder.EyesIris; - break; - case TextureIndex.Skirt: - type = BakeType.Skirt; - order = BakeLayer.BakeOrder.Skirt; - break; - default: - Client.Log("Image downloaded for unknown TextureIndex " + index.ToString(), - Helpers.LogLevel.Warning); - break; - } - - if (PendingBakes.ContainsKey(type)) - { - Client.DebugLog("Adding image to bake " + type.ToString()); - - if (PendingBakes[type].AddImage(order, image.AssetData)) - { - Client.DebugLog("Bake " + type.ToString() + " completed"); - - // Create a transactionID and assetID for this upload - LLUUID transactionID = LLUUID.Random(); - LLUUID assetID = transactionID.Combine(Client.Network.SecureSessionID); - - Client.DebugLog("Beginning bake upload with transactionID: " + - transactionID.ToStringHyphenated() + " and assetID: " + assetID.ToStringHyphenated()); - - // Upload the completed bake data - Assets.RequestUpload(transactionID, AssetType.Texture, PendingBakes[type].FinalData, - true, true, false); - - // Add it to a pending uploads list - lock (PendingUploads) PendingUploads.Add(assetID, index); - - // Remove this bake from the pending list - PendingBakes.Remove(type); - - // FIXME: Probably don't need to do this here - // Add this assetID to AgentTextures in the correct position - //AgentTextures[index] = assetID; - } - } - } - else - { - Client.Log("Texture " + image.ID.ToStringHyphenated() + " failed to download, " + - "bake will be incomplete", Helpers.LogLevel.Warning); - } - - 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 - CachedResponseEvent.Set(); - } - } - } - } - - 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"); - } - } - } - } -} +/* + * 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 libsecondlife; +using libsecondlife.Packets; + +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 + { + AppearanceManager.TextureIndex id = (AppearanceManager.TextureIndex)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.BasePermissions = (libsecondlife.Helpers.PermissionType)aw.Permission_Base_Mask; + // w.EveryonePermissions = (libsecondlife.Helpers.PermissionType)aw.Permission_Everyone_Mask; + // w.GroupPermissions = (libsecondlife.Helpers.PermissionType)aw.Permission_Group_Mask; + // w.NextOwnerPermissions = (libsecondlife.Helpers.PermissionType)aw.Permission_Next_Owner_Mask; + // w.OwnerPermissions = (libsecondlife.Helpers.PermissionType)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 class AppearanceManager + { + /// + /// + /// + public enum TextureIndex + { + Unknown = -1, + HeadBodypaint = 0, + UpperShirt, + LowerPants, + EyesIris, + Hair, + UpperBodypaint, + LowerBodypaint, + LowerShoes, + HeadBaked, + UpperBaked, + LowerBaked, + EyesBaked, + LowerSocks, + UpperJacket, + LowerJacket, + 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]; + // 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 int CacheCheckSerialNum = 0; + private uint SetAppearanceSerialNum = 0; + 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); + //} + + //FIXME: + //public void Wear(List iws) + //{ + // DownloadWearables = false; + + // lock (Wearables) lock (AgentTextures) + // { + // foreach (libsecondlife.InventorySystem.InventoryWearable iw in iws) + // { + // WearableData wd = WearableData.FromInventoryWearable(Client, iw); + // Wearables[wd.Wearable.Type] = wd; + // } + // } + + // lock (DownloadQueue) DownloadQueue.Clear(); + + // Thread appearanceThread = new Thread(new ThreadStart(StartWear)); + // appearanceThread.Start(); + //} + + //FIXME: Doable? + //public void WearOutfit(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); + //} + + //public void WearOutfit(string folder) + //{ + // WearOutfit(Client.Inventory.getFolder(folder)); + //} + + /// + /// Build hashes out of the texture assetIDs for each baking layer to + /// ask the simulator whether it has cached copies of each baked layer + /// + public void 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 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(); + + // Send a list of what we are currently wearing + SendAgentWearables(); + + 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 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.Length]; + + lock (Wearables) + { + // Only for debugging output + int count = 0; + + // Build the visual param array + for (int i = 0; i < VisualParams.Params.Length; i++) + { + bool found = false; + set.VisualParam[i] = new AgentSetAppearancePacket.VisualParamBlock(); + + // Try and find this value in our collection of downloaded wearables + foreach (WearableData data in Wearables.Values) + { + TextureIndex t = (TextureIndex)i; + + if (data.Wearable.Params.ContainsKey(t)) + { + set.VisualParam[i].ParamValue = Helpers.FloatToByte(data.Wearable.Params[t], + VisualParams.Params[i].MinValue, VisualParams.Params[i].MaxValue); + found = true; + count++; + break; + } + } + + // Use a default value if we don't have one set for it + if (!found) + { + set.VisualParam[i].ParamValue = Helpers.FloatToByte(VisualParams.Params[i].DefaultValue, + VisualParams.Params[i].MinValue, VisualParams.Params[i].MaxValue); + } + } + + Client.DebugLog("Sending " + 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[25].ParamValue, VisualParams.Params[25].MinValue, + VisualParams.Params[25].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 SendAgentWearables() + { + Client.DebugLog("Wearables contains " + Wearables.Count + " entries"); + + 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 BakedIndexToAgentTextureIndex(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 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) + { + KeyValuePair download = + new KeyValuePair(data.AssetID, assetType); + DownloadQueue.Enqueue(download); + } + } + } + + if (DownloadQueue.Count > 0) + { + KeyValuePair download = DownloadQueue.Dequeue(); + Assets.RequestAsset(download.Key, download.Value, 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) + { + AgentCachedTextureResponsePacket response = (AgentCachedTextureResponsePacket)packet; + Dictionary paramValues = new Dictionary(VisualParams.Params.Length); + + // Build a dictionary of appearance parameter indices and values from the wearables + for (int i = 0; i < VisualParams.Params.Length; i++) + { + bool found = false; + + // Try and find this value in our collection of downloaded wearables + foreach (WearableData data in Wearables.Values) + { + TextureIndex t = (TextureIndex)i; + + if (data.Wearable.Params.ContainsKey(t)) + { + paramValues.Add(i, data.Wearable.Params[t]); + found = true; + break; + } + } + + // Use a default value if we don't have one set for it + if (!found) paramValues.Add(i, VisualParams.Params[i].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); + + // Convert the baked index to an AgentTexture index + if (block.TextureID != LLUUID.Zero && host.Length == 0) + { + TextureIndex index = BakedIndexToAgentTextureIndex((BakeType)block.TextureIndex); + AgentTextures[(int)index] = block.TextureID; + } + else + { + BakeType bakeType = (BakeType)block.TextureIndex; + 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.UpperUndershirt); + imageCount += AddImageDownload(TextureIndex.UpperShirt); + imageCount += AddImageDownload(TextureIndex.UpperJacket); + // FIXME: Where are the gloves? + } + 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 (imageCount > 0 && !PendingBakes.ContainsKey(bakeType)) + { + Client.DebugLog("Initializing bake for " + bakeType.ToString()); + + lock (PendingBakes) + PendingBakes.Add(bakeType, new BakeLayer(Client, 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)) + ImageDownloads.Add(image, index); + + return 1; + } + else + { + return 0; + } + } + + private void Assets_OnAssetReceived(AssetDownload asset) + { + 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)) + { + lock (AgentTextures) + { + foreach (KeyValuePair texture in data.Wearable.Textures) + AgentTextures[(int)texture.Key] = texture.Value; + } + + Client.DebugLog("Imported wearable asset " + data.Wearable.Type.ToString()); + } + 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 + KeyValuePair download = DownloadQueue.Dequeue(); + Assets.RequestAsset(download.Key, download.Value, true); + } + else + { + // Everything is downloaded + WearablesDownloadedEvent.Set(); + } + } + + private void Assets_OnImageReceived(ImageDownload image) + { + lock (ImageDownloads) + { + if (ImageDownloads.ContainsKey(image.ID)) + { + TextureIndex index = ImageDownloads[image.ID]; + BakeType type = BakeType.Head; + BakeLayer.BakeOrder order = BakeLayer.BakeOrder.HeadBodypaint; + + Client.DebugLog("Finished downloading texture for " + index.ToString()); + + if (image.Success) + { + // Add this image to a baking layer + switch (index) + { + case TextureIndex.HeadBodypaint: + type = BakeType.Head; + order = BakeLayer.BakeOrder.HeadBodypaint; + break; + case TextureIndex.Hair: + type = BakeType.Head; + order = BakeLayer.BakeOrder.Hair; + break; + case TextureIndex.UpperBodypaint: + type = BakeType.UpperBody; + order = BakeLayer.BakeOrder.UpperBodypaint; + break; + case TextureIndex.UpperUndershirt: + type = BakeType.UpperBody; + order = BakeLayer.BakeOrder.UpperUndershirt; + break; + case TextureIndex.UpperShirt: + type = BakeType.UpperBody; + order = BakeLayer.BakeOrder.UpperShirt; + break; + case TextureIndex.UpperJacket: + type = BakeType.UpperBody; + order = BakeLayer.BakeOrder.UpperJacket; + break; + case TextureIndex.LowerBodypaint: + type = BakeType.LowerBody; + order = BakeLayer.BakeOrder.LowerBodypaint; + break; + case TextureIndex.LowerUnderpants: + type = BakeType.LowerBody; + order = BakeLayer.BakeOrder.LowerUnderpants; + break; + case TextureIndex.LowerSocks: + type = BakeType.LowerBody; + order = BakeLayer.BakeOrder.LowerSocks; + break; + case TextureIndex.LowerShoes: + type = BakeType.LowerBody; + order = BakeLayer.BakeOrder.LowerShoes; + break; + case TextureIndex.LowerPants: + type = BakeType.LowerBody; + order = BakeLayer.BakeOrder.LowerPants; + break; + case TextureIndex.LowerJacket: + type = BakeType.LowerBody; + order = BakeLayer.BakeOrder.LowerJacket; + break; + case TextureIndex.EyesIris: + type = BakeType.Eyes; + order = BakeLayer.BakeOrder.EyesIris; + break; + case TextureIndex.Skirt: + type = BakeType.Skirt; + order = BakeLayer.BakeOrder.Skirt; + break; + default: + Client.Log("Image downloaded for unknown TextureIndex " + index.ToString(), + Helpers.LogLevel.Warning); + break; + } + + if (PendingBakes.ContainsKey(type)) + { + Client.DebugLog("Adding image to bake " + type.ToString()); + + if (PendingBakes[type].AddImage(order, image.AssetData)) + { + Client.DebugLog("Bake " + type.ToString() + " completed"); + + // Create a transactionID and assetID for this upload + LLUUID transactionID = LLUUID.Random(); + LLUUID assetID = transactionID.Combine(Client.Network.SecureSessionID); + + Client.DebugLog("Beginning bake upload with transactionID: " + + transactionID.ToStringHyphenated() + " and assetID: " + assetID.ToStringHyphenated()); + + // Upload the completed bake data + Assets.RequestUpload(transactionID, AssetType.Texture, PendingBakes[type].FinalData, + true, true, false); + + // Add it to a pending uploads list + lock (PendingUploads) PendingUploads.Add(assetID, index); + + // Remove this bake from the pending list + PendingBakes.Remove(type); + + // FIXME: Probably don't need to do this here + // Add this assetID to AgentTextures in the correct position + //AgentTextures[index] = assetID; + } + } + } + else + { + Client.Log("Texture " + image.ID.ToStringHyphenated() + " failed to download, " + + "bake will be incomplete", Helpers.LogLevel.Warning); + } + + 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 + CachedResponseEvent.Set(); + } + } + } + } + + 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/libsecondlife.Utilities/Assets.cs b/libsecondlife/AssetManager.cs similarity index 82% rename from libsecondlife/libsecondlife.Utilities/Assets.cs rename to libsecondlife/AssetManager.cs index 9aac397a..d07f5fc9 100644 --- a/libsecondlife/libsecondlife.Utilities/Assets.cs +++ b/libsecondlife/AssetManager.cs @@ -1,835 +1,871 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using libsecondlife; -using libsecondlife.Packets; - -namespace libsecondlife.Utilities.Assets -{ - /// - /// The different types of assets in Second Life - /// - public enum AssetType - { - /// 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; - - internal ManualResetEvent HeaderReceivedEvent = new ManualResetEvent(false); - } - - /// - /// - /// - public class ImageDownload : Transfer - { - public ushort PacketCount = 0; - public int Codec = 0; - public bool NotFound = false; - - internal int InitialDataSize = 0; - internal ManualResetEvent HeaderReceivedEvent = new ManualResetEvent(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; - - // 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); - } - - /// - /// - /// - /// 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; - - // 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); - } - - 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; - - // 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); - } - 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.DebugLog("Asset upload fits in one packet"); - // The whole asset will fit in this packet, makes things easy - request.AssetBlock.AssetData = data; - upload.Transferred = data.Length; - } - else - { - Client.DebugLog("Asset upload will be multiple packets"); - // Asset is too big, send in multiple packets - request.AssetBlock.AssetData = new byte[0]; - } - - // 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.DebugLog(String.Format("Beginning asset upload, ID: {0}, AssetID: {1}, Size: {2}", - upload.ID.ToStringHyphenated(), upload.AssetID.ToStringHyphenated(), upload.Size)); - - Client.DebugLog(request.ToString()); - 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) - { - 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); - // TODO: Set the authoritative asset type here as well - } - else if (transfer.Source == SourceType.SimInventoryItem && info.TransferInfo.Params.Length == 100) - { - transfer.AssetID = new LLUUID(info.TransferInfo.Params, 80); - // TODO: Set the authoritative asset type here as well - } - 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) - { - // 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); - - 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 - Array.Copy(asset.TransferData.Data, 0, transfer.AssetData, 1000 * asset.TransferData.Packet, - asset.TransferData.Data.Length); - transfer.Transferred += asset.TransferData.Data.Length; - - //Client.DebugLog("Received " + asset.TransferData.Data.Length + "/" + transfer.Transferred + - // "/" + transfer.Size + " bytes for asset " + transfer.ID.ToStringHyphenated()); - - // Check if we downloaded the full asset - if (transfer.Transferred >= transfer.Size) - { - 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); } - } - } - } - } - - 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.ToString(), upload.Transferred, upload.Size)); - - if (upload.Transferred < upload.Size) - SendNextUploadPacket((AssetUpload)Transfers[transferID]); - } - } - - //if (upload != null && upload.Success == true && OnAssetUploaded != null) - //{ - // try { OnAssetUploaded(upload); } - // catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - //} - } - - private void AssetUploadCompleteHandler(Packet packet, Simulator simulator) - { - AssetUploadCompletePacket complete = (AssetUploadCompletePacket)packet; - //Client.Log("Received an AssetUploadComplete packet, that's strange...: " + complete.ToString(), - // Helpers.LogLevel.Error); - - 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; - - Client.DebugLog("Received first " + data.ImageData.Data.Length + " bytes for image " + - data.ImageID.ID.ToStringHyphenated()); - - lock (Transfers) - { - if (Transfers.ContainsKey(data.ImageID.ID)) - { - transfer = (ImageDownload)Transfers[data.ImageID.ID]; - - transfer.Codec = data.ImageID.Codec; - transfer.PacketCount = data.ImageID.Packets; - transfer.Size = (int)data.ImageID.Size; - transfer.AssetData = new byte[transfer.Size]; - Array.Copy(data.ImageData.Data, transfer.AssetData, 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) - { - if (OnImageReceived != null && transfer.Transferred >= transfer.Size) - { - try { OnImageReceived(transfer); } - catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - } - - transfer.HeaderReceivedEvent.Set(); - } - } - - /// - /// 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 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; + + 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/Helpers.cs b/libsecondlife/Helpers.cs index b3bbea74..b080e7c2 100644 --- a/libsecondlife/Helpers.cs +++ b/libsecondlife/Helpers.cs @@ -74,36 +74,6 @@ namespace libsecondlife Debug }; - /// - /// - /// - [Flags] - public enum PermissionWho - { - /// - Group = 4, - /// - Everyone = 8, - /// - NextOwner = 16 - } - - /// - /// - /// - [Flags] - public enum PermissionType - { - /// - Copy = 0x00008000, - /// - Modify = 0x00004000, - /// - Move = 0x00080000, - /// - Transfer = 0x00002000 - } - /// Provide a single instance of the MD5 class to avoid making /// duplicate copies public static System.Security.Cryptography.MD5 MD5Builder = @@ -333,12 +303,25 @@ namespace libsecondlife /// public static float BytesToFloat(byte[] bytes, int pos) { - // FIXME: This is bad, just like the conversions done in _Packets_.cs - // We need Mono.DataConverter badly! if (!BitConverter.IsLittleEndian) Array.Reverse(bytes, pos, 4); return BitConverter.ToSingle(bytes, pos); } + /// + /// Convert a floating point value to four bytes in little endian + /// ordering + /// + /// A floating point value + /// A four byte array containing the value in little endian + /// ordering + public static byte[] FloatToBytes(float value) + { + byte[] bytes = BitConverter.GetBytes(value); + if (!BitConverter.IsLittleEndian) + Array.Reverse(bytes); + return bytes; + } + /// /// Converts a floating point number to a terse string format used for /// transmitting numbers in wearable asset files diff --git a/libsecondlife/InventorySystem/InventoryImage.cs b/libsecondlife/InventorySystem/InventoryImage.cs index d8814f16..9e96d9dc 100644 --- a/libsecondlife/InventorySystem/InventoryImage.cs +++ b/libsecondlife/InventorySystem/InventoryImage.cs @@ -23,7 +23,7 @@ namespace libsecondlife.InventorySystem if ((AssetID != null)) { AssetRequestDownload request = base.iManager.AssetManager.RequestInventoryAsset(this); - if (request.Wait(AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) + if (request.Wait(libsecondlife.AssetSystem.AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) { throw new Exception("Asset (" + AssetID.ToStringHyphenated() + ") unavailable (" + request.StatusMsg + ") for " + this.Name); } diff --git a/libsecondlife/InventorySystem/InventoryItem.cs b/libsecondlife/InventorySystem/InventoryItem.cs index fa7c10cf..838e5574 100644 --- a/libsecondlife/InventorySystem/InventoryItem.cs +++ b/libsecondlife/InventorySystem/InventoryItem.cs @@ -131,7 +131,7 @@ namespace libsecondlife.InventorySystem if ((AssetID != null)) { AssetRequestDownload request = base.iManager.AssetManager.RequestInventoryAsset(this); - if (request.Wait(AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) + if (request.Wait(libsecondlife.AssetSystem.AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) { throw new Exception("Asset (" + AssetID.ToStringHyphenated() + ") unavailable (" + request.StatusMsg + ") for " + this.Name); } diff --git a/libsecondlife/InventorySystem/InventoryLandmark.cs b/libsecondlife/InventorySystem/InventoryLandmark.cs index 3c28ba0e..f3da2d9e 100644 --- a/libsecondlife/InventorySystem/InventoryLandmark.cs +++ b/libsecondlife/InventorySystem/InventoryLandmark.cs @@ -94,8 +94,8 @@ namespace libsecondlife.InventorySystem } private string grabAsset( LLUUID AssetID ) { - AssetRequestDownload request = base.iManager.AssetManager.RequestInventoryAsset(this); - if (request.Wait(AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) + AssetRequestDownload request = base.iManager.AssetManager.RequestInventoryAsset(this); + if (request.Wait(libsecondlife.AssetSystem.AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) { throw new Exception("Asset (" + AssetID.ToStringHyphenated() + ") unavailable (" + request.StatusMsg + ") for " + this.Name); } diff --git a/libsecondlife/InventorySystem/InventoryManager.cs b/libsecondlife/InventorySystem/InventoryManager.cs index a33d269a..39facec4 100644 --- a/libsecondlife/InventorySystem/InventoryManager.cs +++ b/libsecondlife/InventorySystem/InventoryManager.cs @@ -51,7 +51,7 @@ namespace libsecondlife.InventorySystem // private ManualResetEvent InventoryManagerInitialized = new ManualResetEvent(false); // Reference to the Asset Manager - internal AssetManager AssetManager + internal libsecondlife.AssetSystem.AssetManager AssetManager { get { return slClient.Assets; } } diff --git a/libsecondlife/InventorySystem/InventoryNotecard.cs b/libsecondlife/InventorySystem/InventoryNotecard.cs index 097ea0e1..36d3b19a 100644 --- a/libsecondlife/InventorySystem/InventoryNotecard.cs +++ b/libsecondlife/InventorySystem/InventoryNotecard.cs @@ -21,7 +21,7 @@ namespace libsecondlife.InventorySystem if ((AssetID != null)) { AssetRequestDownload request = base.iManager.AssetManager.RequestInventoryAsset(this); - if (request.Wait(AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) + if (request.Wait(libsecondlife.AssetSystem.AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) { throw new Exception("Asset (" + AssetID.ToStringHyphenated() + ") unavailable (" + request.StatusMsg + ") for " + this.Name); } diff --git a/libsecondlife/InventorySystem/InventoryScript.cs b/libsecondlife/InventorySystem/InventoryScript.cs index 9d352efd..702ad730 100644 --- a/libsecondlife/InventorySystem/InventoryScript.cs +++ b/libsecondlife/InventorySystem/InventoryScript.cs @@ -20,7 +20,7 @@ namespace libsecondlife.InventorySystem if ( (AssetID != null) ) { AssetRequestDownload request = base.iManager.AssetManager.RequestInventoryAsset(this); - if (request.Wait(AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) + if (request.Wait(libsecondlife.AssetSystem.AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success) { throw new Exception("Asset (" + AssetID.ToStringHyphenated() + ") unavailable (" + request.StatusMsg + ") for " + this.Name); } diff --git a/libsecondlife/LLObject.cs b/libsecondlife/LLObject.cs index 7bd93657..b7db2ecb 100644 --- a/libsecondlife/LLObject.cs +++ b/libsecondlife/LLObject.cs @@ -106,6 +106,29 @@ namespace libsecondlife ZlibCompressed = 0x80000000 } + /// + /// Material type for a primitive + /// + public enum MaterialType : byte + { + /// + Stone = 0, + /// + Metal, + /// + Glass, + /// + Wood, + /// + Flesh, + /// + Plastic, + /// + Rubber, + /// + Light + } + #endregion Enumerations @@ -134,7 +157,7 @@ namespace libsecondlife /// public float PathScaleY; /// - public uint Material; + public MaterialType Material; /// public float PathShearX; /// diff --git a/libsecondlife/ObjectManager.cs b/libsecondlife/ObjectManager.cs index e86faa40..56cde8f9 100644 --- a/libsecondlife/ObjectManager.cs +++ b/libsecondlife/ObjectManager.cs @@ -32,6 +32,21 @@ using libsecondlife.Packets; namespace libsecondlife { + /// + /// + /// + public enum SaleType : byte + { + /// + Not = 0, + /// + Original = 1, + /// + Copy = 2, + /// + Contents = 3 + } + /// /// Contains the variables sent in an object update packet for objects. /// Used to track position and movement of prims and avatars @@ -347,21 +362,6 @@ namespace libsecondlife Undergrowth1 } - /// - /// - /// - public enum SaleType : byte - { - /// - Not = 0, - /// - Original = 1, - /// - Copy = 2, - /// - Contents = 3 - } - /// /// /// @@ -644,6 +644,20 @@ namespace libsecondlife Client.Network.SendPacket(select, simulator); } + public void DeselectObject(Simulator simulator, uint localID) + { + ObjectDeselectPacket deselect = new ObjectDeselectPacket(); + + deselect.AgentData.AgentID = Client.Network.AgentID; + deselect.AgentData.SessionID = Client.Network.SessionID; + + deselect.ObjectData = new ObjectDeselectPacket.ObjectDataBlock[1]; + deselect.ObjectData[0] = new ObjectDeselectPacket.ObjectDataBlock(); + deselect.ObjectData[0].ObjectLocalID = localID; + + Client.Network.SendPacket(deselect, simulator); + } + /// /// /// @@ -843,16 +857,8 @@ namespace libsecondlife extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); extra.ObjectData[0].ObjectLocalID = localID; extra.ObjectData[0].ParamType = (byte)Primitive.ExtraParamType.Light; - if (light == null) - { - extra.ObjectData[0].ParamInUse = false; - extra.ObjectData[0].ParamData = new byte[0]; - } - else - { - extra.ObjectData[0].ParamInUse = true; - extra.ObjectData[0].ParamData = light.GetBytes(); - } + extra.ObjectData[0].ParamInUse = true; + extra.ObjectData[0].ParamData = light.GetBytes(); extra.ObjectData[0].ParamSize = (uint)extra.ObjectData[0].ParamData.Length; Client.Network.SendPacket(extra, simulator); @@ -874,21 +880,63 @@ namespace libsecondlife extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); extra.ObjectData[0].ObjectLocalID = localID; extra.ObjectData[0].ParamType = (byte)Primitive.ExtraParamType.Flexible; - if (flexible == null) - { - extra.ObjectData[0].ParamInUse = false; - extra.ObjectData[0].ParamData = new byte[0]; - } - else - { - extra.ObjectData[0].ParamInUse = true; - extra.ObjectData[0].ParamData = flexible.GetBytes(); - } + extra.ObjectData[0].ParamInUse = true; + extra.ObjectData[0].ParamData = flexible.GetBytes(); extra.ObjectData[0].ParamSize = (uint)extra.ObjectData[0].ParamData.Length; Client.Network.SendPacket(extra, simulator); } + public void SetSculpt(Simulator simulator, uint localID, Primitive.SculptData sculpt) + { + ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket(); + + extra.AgentData.AgentID = Client.Network.AgentID; + extra.AgentData.SessionID = Client.Network.SessionID; + + extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1]; + extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); + extra.ObjectData[0].ObjectLocalID = localID; + extra.ObjectData[0].ParamType = (byte)Primitive.ExtraParamType.Sculpt; + extra.ObjectData[0].ParamInUse = true; + extra.ObjectData[0].ParamData = sculpt.GetBytes(); + extra.ObjectData[0].ParamSize = (uint)extra.ObjectData[0].ParamData.Length; + + Client.Network.SendPacket(extra, simulator); + + // Not sure why, but if you don't send this the sculpted prim disappears + ObjectShapePacket shape = new ObjectShapePacket(); + + shape.AgentData.AgentID = Client.Network.AgentID; + shape.AgentData.SessionID = Client.Network.SessionID; + + shape.ObjectData = new libsecondlife.Packets.ObjectShapePacket.ObjectDataBlock[1]; + shape.ObjectData[0] = new libsecondlife.Packets.ObjectShapePacket.ObjectDataBlock(); + shape.ObjectData[0].ObjectLocalID = localID; + shape.ObjectData[0].PathScaleX = 100; + shape.ObjectData[0].PathScaleY = 150; + shape.ObjectData[0].PathCurve = 32; + + Client.Network.SendPacket(shape, simulator); + } + + public void SetExtraParamOff(Simulator simulator, uint localID, Primitive.ExtraParamType type) + { + ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket(); + + extra.AgentData.AgentID = Client.Network.AgentID; + extra.AgentData.SessionID = Client.Network.SessionID; + extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1]; + extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); + extra.ObjectData[0].ObjectLocalID = localID; + extra.ObjectData[0].ParamType = (byte)type; + extra.ObjectData[0].ParamInUse = false; + extra.ObjectData[0].ParamData = new byte[0]; + extra.ObjectData[0].ParamSize = 0; + + Client.Network.SendPacket(extra, simulator); + } + /// /// /// @@ -1079,28 +1127,27 @@ namespace libsecondlife /// /// /// - public void SetPermissions(Simulator simulator, List localIDs, Helpers.PermissionWho who, - Helpers.PermissionType permissions, bool set) + public void SetPermissions(Simulator simulator, List localIDs, PermissionWho who, + PermissionMask permissions, bool set) { ObjectPermissionsPacket packet = new ObjectPermissionsPacket(); packet.AgentData.AgentID = Client.Network.AgentID; packet.AgentData.SessionID = Client.Network.SessionID; + // Override can only be used by gods packet.HeaderData.Override = false; packet.ObjectData = new ObjectPermissionsPacket.ObjectDataBlock[localIDs.Count]; - int i = 0; - foreach (uint localID in localIDs) + for (int i = 0; i < localIDs.Count; i++) { packet.ObjectData[i] = new ObjectPermissionsPacket.ObjectDataBlock(); - packet.ObjectData[i].ObjectLocalID = localID; + + packet.ObjectData[i].ObjectLocalID = localIDs[i]; packet.ObjectData[i].Field = (byte)who; packet.ObjectData[i].Mask = (uint)permissions; packet.ObjectData[i].Set = Convert.ToByte(set); - - i++; } Client.Network.SendPacket(packet, simulator); @@ -1219,7 +1266,7 @@ namespace libsecondlife #region Decode Object (primitive) parameters LLObject.ObjectData data = new LLObject.ObjectData(); data.State = block.State; - data.Material = block.Material; + data.Material = (LLObject.MaterialType)block.Material; data.PathCurve = block.PathCurve; data.ProfileCurve = block.ProfileCurve; data.PathBegin = LLObject.PathBeginFloat(block.PathBegin); @@ -1407,17 +1454,7 @@ namespace libsecondlife prim.Textures = new LLObject.TextureEntry(block.TextureEntry, 0, block.TextureEntry.Length); - //DEBUG - //LLObject.TextureEntry2 te2 = new LLObject.TextureEntry2(block.TextureEntry, 0, - // block.TextureEntry.Length); - //byte[] te1b = prim.Textures.ToBytes(); - //byte[] te2b = te2.ToBytes(); - //if (te1b != te2b) - // Console.WriteLine("OHNOES!"); - //if (te1b.Length != te2b.Length) - // Console.WriteLine("RUINED!"); - - prim.TextureAnim = new LLObject.TextureAnimation(block.TextureAnim, 0); + prim.TextureAnim = new Primitive.TextureAnimation(block.TextureAnim, 0); prim.ParticleSys = new Primitive.ParticleSystem(block.PSBlock, 0); prim.SetExtraParamsFromBytes(block.ExtraParams, 0); @@ -1736,7 +1773,7 @@ namespace libsecondlife // CRC i += 4; // Material - prim.Data.Material = (uint)block.Data[i++]; + prim.Data.Material = (LLObject.MaterialType)block.Data[i++]; // Click action prim.ClickAction = (ClickAction)block.Data[i++]; // Scale @@ -1765,7 +1802,7 @@ namespace libsecondlife // CRC i += 4; // Material - prim.Data.Material = (uint)block.Data[i++]; + prim.Data.Material = (LLObject.MaterialType)block.Data[i++]; // Click action prim.ClickAction = (ClickAction)block.Data[i++]; // Scale @@ -1949,7 +1986,7 @@ namespace libsecondlife //int textureAnimLength = (int)(block.Data[i++] + (block.Data[i++] << 8) + // (block.Data[i++] << 16) + (block.Data[i++] << 24)); i += 4; - prim.TextureAnim = new LLObject.TextureAnimation(block.Data, i); + prim.TextureAnim = new Primitive.TextureAnimation(block.Data, i); } #endregion @@ -2088,6 +2125,25 @@ namespace libsecondlife #region Utility Functions + /// + /// Setup the ObjectData parameters for a basic wooden cube prim + /// + /// ObjectData struct representing a basic wooden cube prim + public static LLObject.ObjectData BuildCube() + { + LLObject.ObjectData prim = new LLObject.ObjectData(); + + prim.PCode = ObjectManager.PCode.Prim; + prim.Material = LLObject.MaterialType.Wood; + prim.ProfileCurve = 0x01; + prim.PathCurve = 0x10; + prim.ProfileEnd = 1.0f; + prim.PathEnd = 1.0f; + prim.PathRevolutions = 1.0f; + + return prim; + } + protected void SetAvatarSelfSittingOn(uint localid) { Client.Self.sittingOn = localid; diff --git a/libsecondlife/ParticleSystem.cs b/libsecondlife/ParticleSystem.cs index b4d56bec..07bd299a 100644 --- a/libsecondlife/ParticleSystem.cs +++ b/libsecondlife/ParticleSystem.cs @@ -11,7 +11,7 @@ namespace libsecondlife /// /// [Serializable] - public class ParticleSystem + public struct ParticleSystem { /// /// @@ -99,7 +99,6 @@ namespace libsecondlife public LLVector3 PartAcceleration; public LLUUID Texture; public LLUUID Target; - // public ParticleDataFlags PartDataFlags; public float PartMaxAge; public LLColor PartStartColor; @@ -109,14 +108,6 @@ namespace libsecondlife public float PartEndScaleX; public float PartEndScaleY; - - /// - /// - /// - public ParticleSystem() - { - } - /// /// /// @@ -124,7 +115,66 @@ namespace libsecondlife /// public ParticleSystem(byte[] data, int pos) { - FromBytes(data, pos); + // TODO: Not sure exactly how many bytes we need here, so partial + // (but truncated) data will cause an exception to be thrown + if (data.Length > 0) + { + BitPack pack = new BitPack(data, pos); + + CRC = pack.UnpackUBits(32); + PartFlags = pack.UnpackUBits(32); + Pattern = (SourcePattern)pack.UnpackByte(); + MaxAge = pack.UnpackFixed(false, 8, 8); + StartAge = pack.UnpackFixed(false, 8, 8); + InnerAngle = pack.UnpackFixed(false, 3, 5); + OuterAngle = pack.UnpackFixed(false, 3, 5); + BurstRate = pack.UnpackFixed(false, 8, 8); + BurstRadius = pack.UnpackFixed(false, 8, 8); + BurstSpeedMin = pack.UnpackFixed(false, 8, 8); + BurstSpeedMax = pack.UnpackFixed(false, 8, 8); + BurstPartCount = pack.UnpackByte(); + float x = pack.UnpackFixed(true, 8, 7); + float y = pack.UnpackFixed(true, 8, 7); + float z = pack.UnpackFixed(true, 8, 7); + AngularVelocity = new LLVector3(x, y, z); + x = pack.UnpackFixed(true, 8, 7); + y = pack.UnpackFixed(true, 8, 7); + z = pack.UnpackFixed(true, 8, 7); + PartAcceleration = new LLVector3(x, y, z); + Texture = pack.UnpackUUID(); + Target = pack.UnpackUUID(); + + PartDataFlags = (ParticleDataFlags)pack.UnpackUBits(32); + PartMaxAge = pack.UnpackFixed(false, 8, 8); + byte r = pack.UnpackByte(); + byte g = pack.UnpackByte(); + byte b = pack.UnpackByte(); + byte a = pack.UnpackByte(); + PartStartColor = new LLColor(r, g, b, a); + r = pack.UnpackByte(); + g = pack.UnpackByte(); + b = pack.UnpackByte(); + a = pack.UnpackByte(); + PartEndColor = new LLColor(r, g, b, a); + PartStartScaleX = pack.UnpackFixed(false, 3, 5); + PartStartScaleY = pack.UnpackFixed(false, 3, 5); + PartEndScaleX = pack.UnpackFixed(false, 3, 5); + PartEndScaleY = pack.UnpackFixed(false, 3, 5); + } + else + { + CRC = PartFlags = 0; + Pattern = SourcePattern.None; + MaxAge = StartAge = InnerAngle = OuterAngle = BurstRate = BurstRadius = BurstSpeedMin = + BurstSpeedMax = 0.0f; + BurstPartCount = 0; + AngularVelocity = PartAcceleration = LLVector3.Zero; + Texture = Target = LLUUID.Zero; + PartDataFlags = ParticleDataFlags.None; + PartMaxAge = 0.0f; + PartStartColor = PartEndColor = LLColor.Black; + PartStartScaleX = PartStartScaleY = PartEndScaleX = PartEndScaleY = 0.0f; + } } /// @@ -168,59 +218,6 @@ namespace libsecondlife return bytes; } - - /// - /// - /// - /// - /// - private void FromBytes(byte[] data, int pos) - { - if (data.Length == 0) - return; - - BitPack pack = new BitPack(data, pos); - - CRC = pack.UnpackUBits(32); - PartFlags = pack.UnpackUBits(32); - Pattern = (SourcePattern)pack.UnpackByte(); - MaxAge = pack.UnpackFixed(false, 8, 8); - StartAge = pack.UnpackFixed(false, 8, 8); - InnerAngle = pack.UnpackFixed(false, 3, 5); - OuterAngle = pack.UnpackFixed(false, 3, 5); - BurstRate = pack.UnpackFixed(false, 8, 8); - BurstRadius = pack.UnpackFixed(false, 8, 8); - BurstSpeedMin = pack.UnpackFixed(false, 8, 8); - BurstSpeedMax = pack.UnpackFixed(false, 8, 8); - BurstPartCount = pack.UnpackByte(); - float x = pack.UnpackFixed(true, 8, 7); - float y = pack.UnpackFixed(true, 8, 7); - float z = pack.UnpackFixed(true, 8, 7); - AngularVelocity = new LLVector3(x, y, z); - x = pack.UnpackFixed(true, 8, 7); - y = pack.UnpackFixed(true, 8, 7); - z = pack.UnpackFixed(true, 8, 7); - PartAcceleration = new LLVector3(x, y, z); - Texture = pack.UnpackUUID(); - Target = pack.UnpackUUID(); - - PartDataFlags = (ParticleDataFlags)pack.UnpackUBits(32); - PartMaxAge = pack.UnpackFixed(false, 8, 8); - byte r = pack.UnpackByte(); - byte g = pack.UnpackByte(); - byte b = pack.UnpackByte(); - byte a = pack.UnpackByte(); - PartStartColor = new LLColor(r, g, b, a); - r = pack.UnpackByte(); - g = pack.UnpackByte(); - b = pack.UnpackByte(); - a = pack.UnpackByte(); - PartEndColor = new LLColor(r, g, b, a); - PartStartScaleX = pack.UnpackFixed(false, 3, 5); - PartStartScaleY = pack.UnpackFixed(false, 3, 5); - PartEndScaleX = pack.UnpackFixed(false, 3, 5); - PartEndScaleY = pack.UnpackFixed(false, 3, 5); - } } } } diff --git a/libsecondlife/Permissions.cs b/libsecondlife/Permissions.cs new file mode 100644 index 00000000..150c1700 --- /dev/null +++ b/libsecondlife/Permissions.cs @@ -0,0 +1,96 @@ +/* + * 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; + +namespace libsecondlife +{ + /// + /// + /// + [Flags] + public enum PermissionMask : uint + { + None = 0, + Transfer = 1 << 13, + Modify = 1 << 14, + Copy = 1 << 15, + [Obsolete] + EnterParcel = 1 << 16, + [Obsolete] + Terraform = 1 << 17, + [Obsolete] + OwnerDebit = 1 << 18, + Move = 1 << 19, + Damage = 1 << 20, + All = 0x7FFFFFFF + } + + /// + /// + /// + [Flags] + public enum PermissionWho : byte + { + /// + Owner = 0x02, + /// + Group = 0x04, + /// + Everyone = 0x08, + /// + NextOwner = 0x10, + /// + All = 0x1E + } + + /// + /// + /// + public struct Permissions + { + public PermissionMask BaseMask; + public PermissionMask EveryoneMask; + public PermissionMask GroupMask; + public PermissionMask NextOwnerMask; + public PermissionMask OwnerMask; + + public Permissions(uint baseMask, uint everyoneMask, uint groupMask, uint nextOwnerMask, uint ownerMask) + { + BaseMask = (PermissionMask)baseMask; + EveryoneMask = (PermissionMask)everyoneMask; + GroupMask = (PermissionMask)groupMask; + NextOwnerMask = (PermissionMask)nextOwnerMask; + OwnerMask = (PermissionMask)ownerMask; + } + + public override string ToString() + { + return String.Format("Base: {0}, Everyone: {1}, Group: {2}, NextOwner: {3}, Owner: {4}", + BaseMask, EveryoneMask, GroupMask, NextOwnerMask, OwnerMask); + } + } +} diff --git a/libsecondlife/Prims.cs b/libsecondlife/Prims.cs index 8f8429a4..2f22ac2f 100644 --- a/libsecondlife/Prims.cs +++ b/libsecondlife/Prims.cs @@ -33,17 +33,22 @@ namespace libsecondlife { public partial class Primitive : LLObject { + #region Enums + /// /// Extra parameters for primitives, these flags are for features that have /// been added after the original ObjectFlags that has all eight bits /// reserved already /// + [Flags] public enum ExtraParamType : ushort { /// Whether this object has flexible parameters Flexible = 0x10, /// Whether this object has light parameters - Light = 0x20 + Light = 0x20, + /// Whether this object is a sculpted prim + Sculpt = 0x30 } /// @@ -65,14 +70,107 @@ namespace libsecondlife Wheel = 4 } + /// + /// + /// + public enum SculptType : byte + { + /// + None = 0, + /// + Sphere = 1, + /// + Torus = 2, + /// + Plane = 3, + /// + Cylinder = 4 + } + + #endregion Enums + #region Subclasses + /// + /// Controls the texture animation of a particular prim + /// + [Serializable] + public struct TextureAnimation + { + /// + public uint Flags; + /// + public uint Face; + /// + public uint SizeX; + /// + public uint SizeY; + /// + public float Start; + /// + public float Length; + /// + public float Rate; + + /// + /// + /// + /// + /// + public TextureAnimation(byte[] data, int pos) + { + if (data.Length >= 16) + { + Flags = (uint)data[pos++]; + Face = (uint)data[pos++]; + SizeX = (uint)data[pos++]; + SizeY = (uint)data[pos++]; + + Start = Helpers.BytesToFloat(data, pos); + Length = Helpers.BytesToFloat(data, pos + 4); + Rate = Helpers.BytesToFloat(data, pos + 8); + } + else + { + Flags = 0; + Face = 0; + SizeX = 0; + SizeY = 0; + + Start = 0.0f; + Length = 0.0f; + Rate = 0.0f; + } + } + + /// + /// + /// + /// + public byte[] GetBytes() + { + byte[] data = new byte[16]; + int pos = 0; + + data[pos++] = (byte)Flags; + data[pos++] = (byte)Face; + data[pos++] = (byte)SizeX; + data[pos++] = (byte)SizeY; + + Helpers.FloatToBytes(Start).CopyTo(data, pos); + Helpers.FloatToBytes(Length).CopyTo(data, pos + 4); + Helpers.FloatToBytes(Rate).CopyTo(data, pos + 4); + + return data; + } + } + /// /// Information on the flexible properties of a primitive /// [Serializable] - public class FlexibleData + public struct FlexibleData { /// public int Softness; @@ -85,14 +183,7 @@ namespace libsecondlife /// public float Tension; /// - public LLVector3 Force = LLVector3.Zero; - - /// - /// - /// - public FlexibleData() - { - } + public LLVector3 Force; /// /// @@ -101,7 +192,26 @@ namespace libsecondlife /// public FlexibleData(byte[] data, int pos) { - FromBytes(data, pos); + if (data.Length >= 5) + { + Softness = ((data[pos] & 0x80) >> 6) | ((data[pos + 1] & 0x80) >> 7); + + Tension = (float)(data[pos++] & 0x7F) / 10.0f; + Drag = (float)(data[pos++] & 0x7F) / 10.0f; + Gravity = (float)(data[pos++] / 10.0f) - 10.0f; + Wind = (float)data[pos++] / 10.0f; + Force = new LLVector3(data, pos); + } + else + { + Softness = 0; + + Tension = 0.0f; + Drag = 0.0f; + Gravity = 0.0f; + Wind = 0.0f; + Force = LLVector3.Zero; + } } /// @@ -113,59 +223,36 @@ namespace libsecondlife byte[] data = new byte[16]; int i = 0; + // Softness is packed in the upper bits of tension and drag data[i] = (byte)((Softness & 2) << 6); data[i + 1] = (byte)((Softness & 1) << 7); - data[i++] |= (byte)((byte)(Tension * 10.0f) & 0x7F); - data[i++] |= (byte)((byte)(Drag * 10.0f) & 0x7F); - data[i++] = (byte)((Gravity + 10.0f) * 10.0f); - data[i++] = (byte)(Wind * 10.0f); + data[i++] |= (byte)((byte)(Tension * 10.01f) & 0x7F); + data[i++] |= (byte)((byte)(Drag * 10.01f) & 0x7F); + data[i++] = (byte)((Gravity + 10.0f) * 10.01f); + data[i++] = (byte)(Wind * 10.01f); Force.GetBytes().CopyTo(data, i); return data; } - - private void FromBytes(byte[] data, int pos) - { - int i = pos; - - Softness = ((data[i] & 0x80) >> 6) | ((data[i + 1] & 0x80) >> 7); - - Tension = (data[i++] & 0x7F) / 10.0f; - Drag = (data[i++] & 0x7F) / 10.0f; - Gravity = (data[i++] / 10.0f) - 10.0f; - Wind = data[i++] / 10.0f; - Force = new LLVector3(data, i); - } } /// /// Information on the light properties of a primitive /// [Serializable] - public class LightData + public struct LightData { /// - public byte R; - /// - public byte G; - /// - public byte B; - /// - public float Intensity; + public LLColor Color; /// public float Radius; /// + public float Cutoff; + /// public float Falloff; - /// - /// - /// - public LightData() - { - } - /// /// /// @@ -173,7 +260,20 @@ namespace libsecondlife /// public LightData(byte[] data, int pos) { - FromBytes(data, pos); + if (data.Length >= 16) + { + Color = new LLColor(data, 0); + Radius = Helpers.BytesToFloat(data, 4); + Cutoff = Helpers.BytesToFloat(data, 8); + Falloff = Helpers.BytesToFloat(data, 12); + } + else + { + Color = LLColor.Black; + Radius = 0.0f; + Cutoff = 0.0f; + Falloff = 0.0f; + } } /// @@ -183,42 +283,47 @@ namespace libsecondlife public byte[] GetBytes() { byte[] data = new byte[16]; - int i = 0; - data[i++] = R; - data[i++] = G; - data[i++] = B; - data[i++] = (byte)(Intensity * 255.0f); - - BitConverter.GetBytes(Radius).CopyTo(data, i); - BitConverter.GetBytes(Falloff).CopyTo(data, i + 8); - - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(data, i, 4); - Array.Reverse(data, i + 8, 4); - } + Color.GetBytes().CopyTo(data, 0); + Helpers.FloatToBytes(Radius).CopyTo(data, 4); + Helpers.FloatToBytes(Cutoff).CopyTo(data, 8); + Helpers.FloatToBytes(Falloff).CopyTo(data, 12); return data; } + } - private void FromBytes(byte[] data, int pos) + /// + /// Information on the sculpt properties of a sculpted primitive + /// + [Serializable] + public struct SculptData + { + public LLUUID SculptTexture; + public SculptType Type; + + public SculptData(byte[] data, int pos) { - int i = pos; - - R = data[i++]; - G = data[i++]; - B = data[i++]; - Intensity = data[i++] / 255.0f; - - if (!BitConverter.IsLittleEndian) + if (data.Length >= 17) { - Array.Reverse(data, i, 4); - Array.Reverse(data, i + 8, 4); + SculptTexture = new LLUUID(data, pos); + Type = (SculptType)data[pos + 16]; } + else + { + SculptTexture = LLUUID.Zero; + Type = SculptType.None; + } + } - Radius = BitConverter.ToSingle(data, i); - Falloff = BitConverter.ToSingle(data, i + 8); + public byte[] GetBytes() + { + byte[] data = new byte[17]; + + SculptTexture.GetBytes().CopyTo(data, 0); + data[16] = (byte)Type; + + return data; } } @@ -228,19 +333,21 @@ namespace libsecondlife #region Public Members /// - public TextureAnimation TextureAnim = new TextureAnimation(); + public TextureAnimation TextureAnim; /// - public FlexibleData Flexible = new FlexibleData(); + public FlexibleData Flexible; /// - public LightData Light = new LightData(); + public LightData Light; /// - public ParticleSystem ParticleSys = new ParticleSystem(); + public SculptData Sculpt; + /// + public ParticleSystem ParticleSys; /// public ObjectManager.ClickAction ClickAction; /// - public LLUUID Sound = LLUUID.Zero; + public LLUUID Sound; /// Identifies the owner of the audio or particle system - public LLUUID OwnerID = LLUUID.Zero; + public LLUUID OwnerID; /// public byte SoundFlags; /// @@ -272,18 +379,9 @@ namespace libsecondlife public override string ToString() { - string output = String.Empty; - - output += "ID: " + ID + ", "; - output += "GroupID: " + GroupID + ", "; - output += "ParentID: " + ParentID + ", "; - output += "LocalID: " + LocalID + ", "; - output += "Flags: " + Flags + ", "; - output += "State: " + Data.State + ", "; - output += "PCode: " + Data.PCode + ", "; - output += "Material: " + Data.Material + ", "; - - return output; + return String.Format("ID: {0}, GroupID: {1}, ParentID: {2}, LocalID: {3}, Flags: {4}, " + + "State: {5}, PCode: {6}, Material: {7}", ID, GroupID, ParentID, LocalID, Flags, Data.State, + Data.PCode, Data.Material); } public void ToXml(XmlWriter xmlWriter) @@ -322,6 +420,8 @@ namespace libsecondlife Flexible = new FlexibleData(data, i); else if (type == ExtraParamType.Light) Light = new LightData(data, i); + else if (type == ExtraParamType.Sculpt) + Sculpt = new SculptData(data, i); i += (int)paramLength; totalLength += (int)paramLength + 6; diff --git a/libsecondlife/SecondLife.cs b/libsecondlife/SecondLife.cs index 7ebef61e..81cf18cd 100644 --- a/libsecondlife/SecondLife.cs +++ b/libsecondlife/SecondLife.cs @@ -65,11 +65,11 @@ namespace libsecondlife /// Group Subsystem public GroupManager Groups; /// Asset Subsystem - public AssetManager Assets; + public libsecondlife.AssetSystem.AssetManager Assets; /// Appearance Subsystem - public AppearanceManager Appearance; + public libsecondlife.AssetSystem.AppearanceManager Appearance; /// Inventory Subsystem - public InventoryManager Inventory; + public libsecondlife.InventorySystem.InventoryManager Inventory; /// Image Subsystem public ImageManager Images; /// Directory searches including classifieds, people, land @@ -104,8 +104,8 @@ namespace libsecondlife Grid = new GridManager(this); Objects = new ObjectManager(this); Groups = new GroupManager(this); - Assets = new AssetManager(this); - Appearance = new AppearanceManager(this); + Assets = new libsecondlife.AssetSystem.AssetManager(this); + Appearance = new libsecondlife.AssetSystem.AppearanceManager(this); Images = new ImageManager(this); Inventory = new InventoryManager(this); Directory = new DirectoryManager(this); diff --git a/libsecondlife/Textures.cs b/libsecondlife/Textures.cs index 6c543661..34119c70 100644 --- a/libsecondlife/Textures.cs +++ b/libsecondlife/Textures.cs @@ -790,79 +790,5 @@ namespace libsecondlife offsetV, rotation, hasAttribute.ToString(), material, media, textureID.ToStringHyphenated()); } } - - /// - /// Controls the texture animation of a particular prim - /// - [Serializable] - public class TextureAnimation - { - /// - public uint Flags; - /// - public uint Face; - /// - public uint SizeX; - /// - public uint SizeY; - /// - public float Start; - /// - public float Length; - /// - public float Rate; - - /// - /// Default constructor - /// - public TextureAnimation() - { - } - - /// - /// - /// - /// - /// - public TextureAnimation(byte[] data, int pos) - { - FromBytes(data, pos); - } - - /// - /// - /// - /// - public byte[] GetBytes() - { - byte[] bytes = new byte[0]; - // FIXME: Finish TextureAnimation GetBytes() function - return bytes; - } - - private void FromBytes(byte[] data, int pos) - { - int i = pos; - - if (data.Length == 0) - return; - - Flags = (uint)data[i++]; - Face = (uint)data[i++]; - SizeX = (uint)data[i++]; - SizeY = (uint)data[i++]; - - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(data, i, 4); - Array.Reverse(data, i + 4, 4); - Array.Reverse(data, i + 8, 4); - } - - Start = BitConverter.ToSingle(data, i); - Length = BitConverter.ToSingle(data, i + 4); - Rate = BitConverter.ToSingle(data, i + 8); - } - } } } diff --git a/libsecondlife/examples/IA_ImageTool/ImageTool.cs b/libsecondlife/examples/IA_ImageTool/ImageTool.cs deleted file mode 100644 index 0f7f621e..00000000 --- a/libsecondlife/examples/IA_ImageTool/ImageTool.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Drawing; -using libsecondlife; -using libsecondlife.InventorySystem; -using libsecondlife.AssetSystem; - -namespace IA_ImageTool -{ - /// - /// Summary description for Class1. - /// - class ImageTool - { - private SecondLife _Client; - private ManualResetEvent ConnectedSignal = new ManualResetEvent(false); - - - private List _ImageIDs = new List(); - private string _FileName; - private bool _Put; - - /// - /// Used to upload/download images. - /// - [STAThread] - static void Main(string[] args) - { - if (args.Length < 5) - { - ImageTool.Usage(); - return; - } - - List uuidList = new List();; - string filename = String.Empty; - bool put = false; - - if (args[3].ToLower().Equals("put")) - { - put = true; - if (args.Length == 6) - { - // TODO: Parse a compression rate from argument 6 - filename = args[5]; - } - else - { - filename = args[4]; - } - } - else if (args[3].ToLower().Equals("getfile")) - { - if (args.Length < 5) - { - ImageTool.Usage(); - return; - } - - foreach( string id in File.ReadAllLines(args[4]) ) - { - uuidList.Add(id); - } - } - else - { - if (args.Length < 6) - { - ImageTool.Usage(); - return; - } - - uuidList.Add(new LLUUID(args[4])); - - if (args.Length == 6) - { - filename = args[5]; - } - else if (!args[4].ToLower().EndsWith(".j2c")) - { - filename = args[4] + ".j2c"; - } - } - - ImageTool it = new ImageTool(uuidList, filename, put); - - if (it.Connect(args[0], args[1], args[2])) - { - if (it.ConnectedSignal.WaitOne(TimeSpan.FromMinutes(1), false)) - { - it.doStuff(); - it.Disconnect(); - } - } - } - - protected ImageTool(List imageIDs, string filename, bool put) - { - _ImageIDs = imageIDs; - _FileName = filename; - _Put = put; - - 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."); - - // Login - if (!_Client.Network.Login(FirstName, LastName, Password, "ImageTool", "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() - { - if (_Put) - { - Console.WriteLine("Reading: " + _FileName); - - byte[] j2cdata = null; - - Bitmap bitmap = (Bitmap)Bitmap.FromFile(_FileName); - j2cdata = OpenJPEGNet.OpenJPEG.EncodeFromImage(bitmap, String.Empty); - - if (j2cdata == null) - { - Console.WriteLine("Failed to compress " + _FileName); - return; - } - - if (!_Client.Inventory.GetRootFolder().RequestDownloadContents(true, false, false).RequestComplete.WaitOne(5000, false)) - { - Console.WriteLine("timeout while downloading root folders, aborting."); - return; - } - - Console.WriteLine("Connecting to your Texture folder..."); - InventoryFolder iFolder = _Client.Inventory.getFolder("Textures"); - - Console.WriteLine("Uploading Texture..."); - InventoryImage image = iFolder.NewImage(_FileName, "ImageTool Upload", j2cdata); - Console.WriteLine("Asset id = " + image.AssetID.ToStringHyphenated()); - } - else - { - foreach( LLUUID ImageID in _ImageIDs ) - { - string FileName; - if (_ImageIDs.Count > 1) - { - FileName = ImageID.ToString(); - } - else - { - FileName = _FileName; - } - - Console.WriteLine("Downloading: " + ImageID); - - int start = Environment.TickCount; - byte[] j2cdata; - - try - { - j2cdata = _Client.Images.RequestImage(ImageID); - - int end = Environment.TickCount; - Console.WriteLine("Elapsed download time, in TickCounts: " + (end - start)); - - Console.WriteLine("Image Data Length: " + j2cdata.Length); - - Console.WriteLine("Writing to: " + FileName + ".tga"); - File.WriteAllBytes(FileName + ".tga", OpenJPEGNet.OpenJPEG.DecodeToTGA(j2cdata)); - } - catch (Exception e) - { - Console.WriteLine("ERROR: Can't download image: " + e.Message); - } - } - } - } - - protected static void Usage() - { - Console.WriteLine("Usage: ImageTool [first] [last] [password] [get] [uuid] [(filename)]"); - Console.WriteLine("Usage: ImageTool [first] [last] [password] [getfile] [filename]"); - Console.WriteLine("Usage: ImageTool [first] [last] [password] [put] [filename]"); - //Console.WriteLine("Usage: ImageTool [first] [last] [password] [put] [bit-rate] [filename]"); - - Console.WriteLine(); - Console.WriteLine("Example: ImageTool John Doe Password get 0444bf21-f77e-7f63-89e9-b839ec66bc15 cloud (this will output cloud.tga)"); - Console.WriteLine("Example: ImageTool John Doe Password getfile uuids.txt (this will download a list of textures, one per line)"); - Console.WriteLine("Example: ImageTool John Doe Password put Sample.jpg"); - //Console.WriteLine("Example: ImageTool John Doe Password put 1.0 BigImage.jpg (this will compress the file with the given bit-rate)"); - } - } -} diff --git a/libsecondlife/examples/IA_ImageTool/Properties/AssemblyInfo.cs b/libsecondlife/examples/IA_ImageTool/Properties/AssemblyInfo.cs deleted file mode 100644 index 4760de8b..00000000 --- a/libsecondlife/examples/IA_ImageTool/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("ImageTool")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ImageTool")] -[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("e01867f5-7ab0-4b1a-aca6-19c7bd764a7b")] - -// 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/IA_LandmarkTool/LandmarkTool.cs b/libsecondlife/examples/IA_LandmarkTool/LandmarkTool.cs deleted file mode 100644 index bd8888df..00000000 --- a/libsecondlife/examples/IA_LandmarkTool/LandmarkTool.cs +++ /dev/null @@ -1,168 +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.Generic; -using System.IO; -using System.Text; -using System.Threading; - -using libsecondlife; -using libsecondlife.Packets; -using libsecondlife.InventorySystem; - -namespace IA_NotecardTool -{ - class LandmarkTool - { - private SecondLife _Client; - private ManualResetEvent ConnectedSignal = new ManualResetEvent(false); - - static void Main(string[] args) - { - if (args.Length < 8) - { - Console.WriteLine("Usage: NotecardTool [first] [last] [password] [landmarkname] [sim] [x] [y] [z]"); - return; - } - - LandmarkTool tool = new LandmarkTool(); - int x = 0; - int y = 0; - int z = 0; - try { - x = int.Parse(args[5]); - y = int.Parse(args[6]); - z = int.Parse(args[7]); - } catch { - Console.WriteLine("Usage: NotecardTool [first] [last] [password] [landmarkname] [sim] [x] [y] [z]"); - return; - } - tool.Connect(args[0], args[1], args[2], args[4], x, y, z); - - if (tool.ConnectedSignal.WaitOne(TimeSpan.FromMinutes(1), false)) - { - tool.doStuff(args[3]); - tool.Disconnect(); - } - } - - private void doStuff(string LandmarkName) - { - Console.WriteLine("Located in " + _Client.Network.CurrentSim.Name + " at " + _Client.Self.Position.ToString()); - Console.WriteLine("Ensuring root folder is there"); - _Client.Inventory.GetRootFolder().RequestDownloadContents(false, true, false).RequestComplete.WaitOne(); - Console.WriteLine("Getting Landmark Folder"); - InventoryFolder iFolder = _Client.Inventory.getFolder("Landmarks"); - Console.WriteLine("Folder retrieved - " + iFolder.FolderID.ToString()); - Console.WriteLine("Creating Landmark"); - iFolder.NewLandmark(LandmarkName, "IA_LandmarkTool"); - Console.WriteLine("Done, now Reading"); - iFolder.RequestDownloadContents(false,false, true).RequestComplete.WaitOne(); - List Landmarks = iFolder.GetItemByName(LandmarkName); - Console.WriteLine(Landmarks.Count.ToString() + " items read"); - foreach ( InventoryBase i in Landmarks) { - if ( i is InventoryItem ) { - InventoryItem ii = (InventoryItem)i; - Console.WriteLine( ii.Name + " - " + ii.ItemID + " is a " + i.GetType()); - if ( ii is InventoryLandmark ) { - InventoryLandmark l = (InventoryLandmark)ii; - Console.WriteLine( "Version is " + l.Version.ToString() + " RegionID is " + l.Region.ToString() + " and pos is " + l.Pos.ToString()); - } - } - } - Console.WriteLine("Trying to mod position to 128,128,128"); - foreach ( InventoryBase i in Landmarks) { - if ( i is InventoryItem ) { - InventoryItem ii = (InventoryItem)i; - Console.WriteLine( ii.Name + " - " + ii.ItemID + " is a " + i.GetType()); - if ( ii is InventoryLandmark ) { - InventoryLandmark l = (InventoryLandmark)ii; - l.Pos = new LLVector3(128.0f, 128.0f, 128.0f); - } - } - } - Console.WriteLine("After mod"); - foreach ( InventoryBase i in Landmarks) { - if ( i is InventoryItem ) { - InventoryItem ii = (InventoryItem)i; - Console.WriteLine( ii.Name + " - " + ii.ItemID + " is a " + i.GetType()); - if ( ii is InventoryLandmark ) { - InventoryLandmark l = (InventoryLandmark)ii; - Console.WriteLine( "Version is " + l.Version.ToString() + " RegionID is " + l.Region.ToString() + " and pos is " + l.Pos.ToString()); - } - } - } - } - - public LandmarkTool() - { - try - { - _Client = new SecondLife(); - //iManager = new InventoryManager(_Client); - _Client.Settings.MULTIPLE_SIMS = false; - _Client.Settings.DEBUG = false; - _Client.Network.OnConnected += new NetworkManager.ConnectedCallback(Network_OnConnected); - _Client.Network.RegisterCallback(PacketType.RegionHandshake, new NetworkManager.PacketCallback(OnRegionHandshake)); - _Client.Network.OnLogin += new NetworkManager.LoginCallback(Network_OnLoginChange); - } - catch (Exception e) - { - // Error initializing the client - Console.WriteLine(); - Console.WriteLine(e.ToString()); - } - } - - void Network_OnConnected(object sender) - { - Console.WriteLine("Connected"); - } - void OnRegionHandshake(Packet packet, Simulator simulator) - { - Console.WriteLine("Handshake received for region " + simulator.Name); - ConnectedSignal.Set(); - } - void Network_OnLoginChange(NetworkManager.LoginStatus login, string message){ - Console.WriteLine("Login Status Changed - " + message); - } - protected bool Connect(string FirstName, string LastName, string Password, string sim, int x, int y, int z) - { - Console.WriteLine("Attempting to connect and login to SecondLife."); - // Login - _Client.Network.Login(FirstName, LastName, Password, "createlandmark", NetworkManager.StartLocation(sim, x, y, z), "jef@pleiades.ca"); - return true; - } - - protected void Disconnect() - { - // Logout of Second Life - Console.WriteLine("Request logout"); - _Client.Network.Logout(); - } - } -} diff --git a/libsecondlife/examples/IA_NotecardTool/IA_NotecardTool.csproj b/libsecondlife/examples/IA_NotecardTool/IA_NotecardTool.csproj deleted file mode 100644 index 4c0b783f..00000000 --- a/libsecondlife/examples/IA_NotecardTool/IA_NotecardTool.csproj +++ /dev/null @@ -1,54 +0,0 @@ - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {E185E4E1-62D2-430C-A94C-E8E38190805B} - Exe - Properties - IA_NotecardTool - NotecardTool - IA_NotecardTool.NotecardTool - - - 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_NotecardTool/MoreSamples.txt b/libsecondlife/examples/IA_NotecardTool/MoreSamples.txt deleted file mode 100644 index d8afc925..00000000 --- a/libsecondlife/examples/IA_NotecardTool/MoreSamples.txt +++ /dev/null @@ -1,2 +0,0 @@ -Additional samples for working with Appearance, Assets, Inventory and Images -can be found at http://code.google.com/p/libsl-ia-samples/ diff --git a/libsecondlife/examples/IA_NotecardTool/NotecardTool.cs b/libsecondlife/examples/IA_NotecardTool/NotecardTool.cs deleted file mode 100644 index 3e75541f..00000000 --- a/libsecondlife/examples/IA_NotecardTool/NotecardTool.cs +++ /dev/null @@ -1,137 +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.Generic; -using System.IO; -using System.Text; -using System.Threading; - -using libsecondlife; -using libsecondlife.InventorySystem; - -namespace IA_NotecardTool -{ - class NotecardTool - { - private string FileName; - private string NotecardName; - - private SecondLife _Client; - private ManualResetEvent ConnectedSignal = new ManualResetEvent(false); - - - static void Main(string[] args) - { - if (args.Length < 6) - { - Console.WriteLine("Usage: NotecardTool [first] [last] [password] [put] [name] [notecard.txt] "); - return; - } - - if( !File.Exists(args[5]) ) - { - Console.WriteLine("Cannot find file: " + args[5]); - return; - } - - NotecardTool tool = new NotecardTool(); - tool.NotecardName = args[4] + " : " + Helpers.GetUnixTime(); - tool.FileName = args[5]; - - tool.Connect(args[0], args[1], args[2]); - - if (tool.ConnectedSignal.WaitOne(TimeSpan.FromMinutes(1), false)) - { - tool.doStuff(); - tool.Disconnect(); - } - } - - private void doStuff() - { - Console.WriteLine("Reading " + FileName); - StreamReader sr = File.OpenText(FileName); - string Body = sr.ReadToEnd(); - - Console.WriteLine("Getting Notecard Folder"); - InventoryFolder iFolder = _Client.Inventory.getFolder("Notecards"); - - - Console.WriteLine("Creating Notecard"); - iFolder.NewNotecard(NotecardName, "Imported by libsl Notecard Tool", Body); - - Console.WriteLine("Done."); - } - - public NotecardTool() - { - 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."); - - // Login - if (!_Client.Network.Login(FirstName, LastName, Password, "createnotecard", "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(); - } - } -} diff --git a/libsecondlife/examples/IA_NotecardTool/Properties/AssemblyInfo.cs b/libsecondlife/examples/IA_NotecardTool/Properties/AssemblyInfo.cs deleted file mode 100644 index f4f4e181..00000000 --- a/libsecondlife/examples/IA_NotecardTool/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_NotecardTool")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("IA_NotecardTool")] -[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("2f12f3fd-adb3-408b-9f70-ed6e9fa25cff")] - -// 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/Commands/Inventory/AppearanceCommand.cs b/libsecondlife/examples/TestClient/Commands/Inventory/AppearanceCommand.cs index 6f003a74..f0fadad4 100644 --- a/libsecondlife/examples/TestClient/Commands/Inventory/AppearanceCommand.cs +++ b/libsecondlife/examples/TestClient/Commands/Inventory/AppearanceCommand.cs @@ -8,16 +8,16 @@ namespace libsecondlife.TestClient { public class AppearanceCommand : Command { - Utilities.Assets.AssetManager Assets; - Utilities.Appearance.AppearanceManager Appearance; + AssetManager Assets; + AppearanceManager Appearance; public AppearanceCommand(TestClient testClient) { Name = "appearance"; Description = "Set your current appearance to your last saved appearance"; - Assets = new libsecondlife.Utilities.Assets.AssetManager(testClient); - Appearance = new libsecondlife.Utilities.Appearance.AppearanceManager(testClient, Assets); + Assets = new AssetManager(testClient); + Appearance = new AppearanceManager(testClient, Assets); } public override string Execute(string[] args, LLUUID fromAgentID) diff --git a/libsecondlife/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs b/libsecondlife/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs index 2d3d0d85..413ec983 100644 --- a/libsecondlife/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs +++ b/libsecondlife/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs @@ -3,14 +3,12 @@ using System.Text; using System.IO; using System.Collections.Generic; using libsecondlife; -using libsecondlife.Utilities.Assets; -using libsecondlife.Utilities.Appearance; namespace libsecondlife.TestClient { public class DumpOutfitCommand : Command { - libsecondlife.Utilities.Assets.AssetManager Assets; + AssetManager Assets; List OutfitAssets = new List(); public DumpOutfitCommand(TestClient testClient) diff --git a/libsecondlife/examples/TestClient/Commands/Prims/ImportCommand.cs b/libsecondlife/examples/TestClient/Commands/Prims/ImportCommand.cs index 4cdcaee8..e1f42681 100644 --- a/libsecondlife/examples/TestClient/Commands/Prims/ImportCommand.cs +++ b/libsecondlife/examples/TestClient/Commands/Prims/ImportCommand.cs @@ -8,32 +8,32 @@ using libsecondlife; namespace libsecondlife.TestClient { - enum ImporterState - { - RezzingParent, - RezzingChildren, - Linking, - Idle - } - - public class Linkset - { - public Primitive RootPrim; - public List Children = new List(); - - public Linkset() - { - RootPrim = new Primitive(); - } - - public Linkset(Primitive rootPrim) - { - RootPrim = rootPrim; - } - } - public class ImportCommand : Command { + private enum ImporterState + { + RezzingParent, + RezzingChildren, + Linking, + Idle + } + + private class Linkset + { + public Primitive RootPrim; + public List Children = new List(); + + public Linkset() + { + RootPrim = new Primitive(); + } + + public Linkset(Primitive rootPrim) + { + RootPrim = rootPrim; + } + } + Primitive currentPrim; LLVector3 currentPosition; SecondLife currentClient; @@ -168,9 +168,8 @@ namespace libsecondlife.TestClient if (primDone.WaitOne(100000 * linkset.Children.Count, false)) { Client.Objects.SetPermissions(Client.Network.CurrentSim, primIDs, - Helpers.PermissionWho.Everyone | Helpers.PermissionWho.Group | Helpers.PermissionWho.NextOwner, - Helpers.PermissionType.Copy | Helpers.PermissionType.Modify | Helpers.PermissionType.Move | - Helpers.PermissionType.Transfer, true); + PermissionWho.Everyone | PermissionWho.Group | PermissionWho.NextOwner, + PermissionMask.All, true); Client.Objects.SetRotation(Client.Network.CurrentSim, rootLocalID, rootRotation); } diff --git a/libsecondlife/examples/groupmanager/frmGroupInfo.cs b/libsecondlife/examples/groupmanager/frmGroupInfo.cs index 4587ac13..eaf11a7e 100644 --- a/libsecondlife/examples/groupmanager/frmGroupInfo.cs +++ b/libsecondlife/examples/groupmanager/frmGroupInfo.cs @@ -7,8 +7,6 @@ using System.Text; using System.Windows.Forms; using System.IO; using libsecondlife; -using libsecondlife.AssetSystem; -using libsecondlife.Utilities.Assets; namespace groupmanager { @@ -25,7 +23,7 @@ namespace groupmanager GroupManager.GroupMembersCallback GroupMembersCallback; GroupManager.GroupTitlesCallback GroupTitlesCallback; AvatarManager.AvatarNamesCallback AvatarNamesCallback; - libsecondlife.Utilities.Assets.AssetManager Assets; + AssetManager Assets; public frmGroupInfo(Group group, SecondLife client) { @@ -44,8 +42,8 @@ namespace groupmanager Group = group; Client = client; - Assets = new libsecondlife.Utilities.Assets.AssetManager(Client); - Assets.OnImageReceived += new libsecondlife.Utilities.Assets.AssetManager.ImageReceivedCallback(Assets_OnImageReceived); + Assets = new AssetManager(Client); + Assets.OnImageReceived += new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); // Register the callbacks for this form Client.Groups.OnGroupProfile += GroupProfileCallback; diff --git a/libsecondlife/libsecondlife.Tests/libsecondlife.Tests.csproj b/libsecondlife/libsecondlife.Tests/libsecondlife.Tests.csproj index cecef19d..1770c0ba 100644 --- a/libsecondlife/libsecondlife.Tests/libsecondlife.Tests.csproj +++ b/libsecondlife/libsecondlife.Tests/libsecondlife.Tests.csproj @@ -31,7 +31,10 @@ ..\..\bin\ - + + False + C:\Program Files\NUnit 2.4.1\bin\nunit.framework.dll + diff --git a/libsecondlife/libsecondlife.Utilities/Inventory.cs b/libsecondlife/libsecondlife.Utilities/Inventory.cs deleted file mode 100644 index 742c0a11..00000000 --- a/libsecondlife/libsecondlife.Utilities/Inventory.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using libsecondlife; -using libsecondlife.Packets; - -namespace libsecondlife.Utilities.Inventory -{ - public enum InventoryType - { - Unknown = -1, - Texture = 0, - Sound = 1, - CallingCard = 2, - Landmark = 3, - [Obsolete] - Script = 4, - [Obsolete] - Clothing = 5, - Object = 6, - Notecard = 7, - Category = 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 - } - - public struct Permissions - { - public uint BaseMask; - public uint EveryoneMask; - public uint GroupMask; - public uint NextOwnerMask; - public uint OwnerMask; - } - - public struct InventoryItem - { - public string Name; - public string Description; - public InventoryType InvType; - public Assets.AssetType AssetType; - public LLUUID AssetID; - public LLUUID ItemID; - public LLUUID OwnerID; - public LLUUID GroupID; - public LLUUID CreatorID; - public LLUUID FolderID; - public bool GroupOwned; - public ObjectManager.SaleType SaleType; - public int SalePrice; - public Permissions Permissions; - public uint Flags; - - public override string ToString() - { - return String.Format("{0} ({1}) InvType: {2} AssetType: {3} AssetID: {4} ItemID: {5}", Name, Description, - InvType.ToString(), AssetType.ToString(), AssetID.ToStringHyphenated(), ItemID.ToStringHyphenated()); - } - } - - - public class InventoryManager - { - public delegate void NewInventoryCallback(InventoryItem item); - - - public event NewInventoryCallback OnNewInventory; - - - private SecondLife Client; - - - public InventoryManager(SecondLife client) - { - Client = client; - - Client.Network.RegisterCallback(PacketType.UpdateCreateInventoryItem, - new NetworkManager.PacketCallback(UpdateCreateInventoryItemHandler)); - } - - public void AttachFromInventory(InventoryItem item, ObjectManager.AttachmentPoint attachPoint) - { - AttachFromInventory(item.ItemID, item.Name, item.Description, item.OwnerID, item.Permissions.EveryoneMask, - item.Permissions.GroupMask, item.Permissions.NextOwnerMask, item.Flags, attachPoint); - } - - public void AttachFromInventory(LLUUID itemID, string name, string description, LLUUID ownerID, - uint everyoneMask, uint groupMask, uint nextOwnerMask, uint flags, - ObjectManager.AttachmentPoint attachPoint) - { - RezSingleAttachmentFromInvPacket rez = new RezSingleAttachmentFromInvPacket(); - - rez.AgentData.AgentID = Client.Network.AgentID; - rez.AgentData.SessionID = Client.Network.SessionID; - - rez.ObjectData.AttachmentPt = (byte)attachPoint; - rez.ObjectData.Description = Helpers.StringToField(description); - rez.ObjectData.EveryoneMask = everyoneMask; - rez.ObjectData.GroupMask = groupMask; - rez.ObjectData.ItemFlags = flags; - rez.ObjectData.ItemID = itemID; - rez.ObjectData.Name = Helpers.StringToField(name); - rez.ObjectData.NextOwnerMask = nextOwnerMask; - rez.ObjectData.OwnerID = ownerID; - - Client.Network.SendPacket(rez); - } - - private void UpdateCreateInventoryItemHandler(Packet packet, Simulator simulator) - { - UpdateCreateInventoryItemPacket create = (UpdateCreateInventoryItemPacket)packet; - - for (int i = 0; i < create.InventoryData.Length; i++) - { - UpdateCreateInventoryItemPacket.InventoryDataBlock block = create.InventoryData[i]; - - InventoryItem item = new InventoryItem(); - item.AssetID = block.AssetID; - item.AssetType = (Assets.AssetType)block.Type; - item.CreatorID = block.CreatorID; - item.Description = Helpers.FieldToUTF8String(block.Description); - item.Flags = block.Flags; - item.FolderID = block.FolderID; - item.GroupID = block.GroupID; - item.GroupOwned = block.GroupOwned; - item.InvType = (InventoryType)block.InvType; - item.ItemID = block.ItemID; - item.Name = Helpers.FieldToUTF8String(block.Name); - item.OwnerID = block.OwnerID; - item.SalePrice = block.SalePrice; - item.SaleType = (ObjectManager.SaleType)block.SaleType; - - item.Permissions.BaseMask = block.BaseMask; - item.Permissions.EveryoneMask = block.EveryoneMask; - item.Permissions.GroupMask = block.GroupMask; - item.Permissions.NextOwnerMask = block.NextOwnerMask; - item.Permissions.OwnerMask = block.OwnerMask; - - if (OnNewInventory != null) - { - try { OnNewInventory(item); } - catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - } - } - } - } -} diff --git a/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj b/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj index d9a23554..964c9aa5 100644 --- a/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj +++ b/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj @@ -34,9 +34,6 @@ - - - diff --git a/libsecondlife/libsecondlife.csproj b/libsecondlife/libsecondlife.csproj index da6258d0..d47cf14a 100644 --- a/libsecondlife/libsecondlife.csproj +++ b/libsecondlife/libsecondlife.csproj @@ -87,9 +87,11 @@ + Code + @@ -151,6 +153,7 @@ Code + Code diff --git a/libsecondlife/libsecondlife.sln b/libsecondlife/libsecondlife.sln index 7574ede1..c75bf689 100644 --- a/libsecondlife/libsecondlife.sln +++ b/libsecondlife/libsecondlife.sln @@ -12,12 +12,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "primexport", "examples\prim EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mapgenerator", "mapgenerator\mapgenerator.csproj", "{C59B1312-57EF-4146-B6B2-1C7B6DC4638B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IA_SimpleInventory", "examples\IA_SimpleInventory\IA_SimpleInventory.csproj", "{E464B963-46E3-4E1A-A36F-9C640C880E68}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IA_NotecardTool", "examples\IA_NotecardTool\IA_NotecardTool.csproj", "{E185E4E1-62D2-430C-A94C-E8E38190805B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IA_ImageTool", "examples\IA_ImageTool\IA_ImageTool.csproj", "{8D2E5240-2247-42F5-AAAC-CF0CCCEE349A}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "groupmanager", "examples\groupmanager\groupmanager.csproj", "{F460FAB3-0D12-4873-89EB-2696818764B8}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "examples\TestClient\TestClient.csproj", "{B87682F6-B2D7-4C4D-A529-400C24FD4880}" @@ -46,6 +40,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatConsole", "..\SLProxy\C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUITestClient", "examples\GUITestClient\GUITestClient.csproj", "{9E0EE72D-AAA7-42EC-8E59-2C3EA5ED6D98}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "importprimscript", "..\importprimscript\importprimscript.csproj", "{32A7AA59-5129-4446-A6DC-2F581ED1A25C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -76,18 +72,6 @@ Global {C59B1312-57EF-4146-B6B2-1C7B6DC4638B}.Debug|Any CPU.Build.0 = Debug|Any CPU {C59B1312-57EF-4146-B6B2-1C7B6DC4638B}.Release|Any CPU.ActiveCfg = Release|Any CPU {C59B1312-57EF-4146-B6B2-1C7B6DC4638B}.Release|Any CPU.Build.0 = Release|Any CPU - {E464B963-46E3-4E1A-A36F-9C640C880E68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E464B963-46E3-4E1A-A36F-9C640C880E68}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E464B963-46E3-4E1A-A36F-9C640C880E68}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E464B963-46E3-4E1A-A36F-9C640C880E68}.Release|Any CPU.Build.0 = Release|Any CPU - {E185E4E1-62D2-430C-A94C-E8E38190805B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E185E4E1-62D2-430C-A94C-E8E38190805B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E185E4E1-62D2-430C-A94C-E8E38190805B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E185E4E1-62D2-430C-A94C-E8E38190805B}.Release|Any CPU.Build.0 = Release|Any CPU - {8D2E5240-2247-42F5-AAAC-CF0CCCEE349A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D2E5240-2247-42F5-AAAC-CF0CCCEE349A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D2E5240-2247-42F5-AAAC-CF0CCCEE349A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D2E5240-2247-42F5-AAAC-CF0CCCEE349A}.Release|Any CPU.Build.0 = Release|Any CPU {F460FAB3-0D12-4873-89EB-2696818764B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F460FAB3-0D12-4873-89EB-2696818764B8}.Debug|Any CPU.Build.0 = Debug|Any CPU {F460FAB3-0D12-4873-89EB-2696818764B8}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -143,6 +127,10 @@ Global {9E0EE72D-AAA7-42EC-8E59-2C3EA5ED6D98}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E0EE72D-AAA7-42EC-8E59-2C3EA5ED6D98}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E0EE72D-AAA7-42EC-8E59-2C3EA5ED6D98}.Release|Any CPU.Build.0 = Release|Any CPU + {32A7AA59-5129-4446-A6DC-2F581ED1A25C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32A7AA59-5129-4446-A6DC-2F581ED1A25C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32A7AA59-5129-4446-A6DC-2F581ED1A25C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32A7AA59-5129-4446-A6DC-2F581ED1A25C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/openjpeg-libsl/libsl/libsl.cpp b/openjpeg-libsl/libsl/libsl.cpp index d5f2fac1..2d01d1e1 100644 --- a/openjpeg-libsl/libsl/libsl.cpp +++ b/openjpeg-libsl/libsl/libsl.cpp @@ -1,6 +1,7 @@ // This is the main DLL file. #include "libsl.h" + extern "C" { #include "../libopenjpeg/openjpeg.h" } @@ -96,7 +97,7 @@ struct cio_wrapper } }; -bool _STDCALL LibslAllocEncoded(LibslImage* image) +bool _stdcall LibslAllocEncoded(LibslImage* image) { try { @@ -112,7 +113,7 @@ bool _STDCALL LibslAllocEncoded(LibslImage* image) return true; } -bool _STDCALL LibslAllocDecoded(LibslImage* image) +bool _stdcall LibslAllocDecoded(LibslImage* image) { try { @@ -128,14 +129,98 @@ bool _STDCALL LibslAllocDecoded(LibslImage* image) return true; } -void _STDCALL LibslFree(LibslImage* image) +void _stdcall LibslFree(LibslImage* image) { if (image->encoded != 0) delete image->encoded; if (image->decoded != 0) delete image->decoded; } -bool _STDCALL LibslEncode(LibslImage* image) +bool _stdcall LibslEncode(LibslImage* image, bool lossless) +{ + try + { + opj_cparameters cparameters; + opj_set_default_encoder_parameters(&cparameters); + cparameters.cp_disto_alloc = 1; + + if (lossless) + { + cparameters.tcp_numlayers = 1; + cparameters.tcp_rates[0] = 0; + } + else + { + cparameters.tcp_numlayers = 6; + cparameters.tcp_rates[0] = 1280; + cparameters.tcp_rates[1] = 640; + cparameters.tcp_rates[2] = 320; + cparameters.tcp_rates[3] = 160; + cparameters.tcp_rates[4] = 80; + cparameters.tcp_rates[5] = 40; + } + cparameters.cp_comment = ""; + + if (image->components > 4) + return false; + + opj_image_comptparm comptparm[5]; + + for (int i = 0; i < image->components; i++) + { + comptparm[i].bpp = 8; + comptparm[i].prec = 8; + comptparm[i].sgnd = 0; + comptparm[i].dx = 1; + comptparm[i].dy = 1; + comptparm[i].x0 = 0; + comptparm[i].y0 = 0; + comptparm[i].w = image->width; + comptparm[i].h = image->height; + } + + image_wrapper cimage(image->components,comptparm,CLRSPC_SRGB); + cimage.image->x0 = 0; + cimage.image->y0 = 0; + cimage.image->x1 = image->width; + cimage.image->y1 = image->height; + + int dataIndex = 0, compIndex = 0, x, y, c; + + for (y = 0; y < image->height; y++) + { + for (x = 0; x < image->width; x++) + { + for (c = 0; c < image->components; c++) + cimage.image->comps[c].data[compIndex] = image->decoded[dataIndex++]; + + compIndex++; + } + } + + cinfo_wrapper cinfo(CODEC_J2K); + opj_setup_encoder(cinfo.cinfo,&cparameters,cimage.image); + cio_wrapper cio(cinfo.cinfo,NULL,0); + + if (!opj_encode(cinfo.cinfo,cio.cio,cimage.image,cparameters.index)) + return false; + + image->length = cio_tell(cio.cio); + image->encoded = new unsigned char[image->length]; + + for (int i = 0; i < image->length; i++) + image->encoded[i] = cio.cio->buffer[i]; + + return true; + } + + catch (...) + { + return false; + } +} + +bool _stdcall LibslEncodeBake(LibslImage* image) { try { @@ -175,42 +260,42 @@ bool _STDCALL LibslEncode(LibslImage* image) cimage.image->x1 = image->width; cimage.image->y1 = image->height; - int dataIndex = 0, compIndex = 0, x, y, c; - - for (y = 0; y < image->height; y++) - { - for (x = 0; x < image->width; x++) - { + int dataIndex = 0, compIndex = 0, x, y, c; + + for (y = 0; y < image->height; y++) + { + for (x = 0; x < image->width; x++) + { for (c = 0; c < image->components; c++) cimage.image->comps[c].data[compIndex] = image->decoded[dataIndex++]; compIndex++; } } - - cinfo_wrapper cinfo(CODEC_J2K); - opj_setup_encoder(cinfo.cinfo,&cparameters,cimage.image); - cio_wrapper cio(cinfo.cinfo,NULL,0); - - if (!opj_encode(cinfo.cinfo,cio.cio,cimage.image,cparameters.index)) - return false; - - image->length = cio_tell(cio.cio); - image->encoded = new unsigned char[image->length]; - - for (int i = 0; i < image->length; i++) - image->encoded[i] = cio.cio->buffer[i]; - - return true; - } - - catch (...) - { - return false; - } + + cinfo_wrapper cinfo(CODEC_J2K); + opj_setup_encoder(cinfo.cinfo,&cparameters,cimage.image); + cio_wrapper cio(cinfo.cinfo,NULL,0); + + if (!opj_encode(cinfo.cinfo,cio.cio,cimage.image,cparameters.index)) + return false; + + image->length = cio_tell(cio.cio); + image->encoded = new unsigned char[image->length]; + + for (int i = 0; i < image->length; i++) + image->encoded[i] = cio.cio->buffer[i]; + + return true; + } + + catch (...) + { + return false; + } } -bool _STDCALL LibslDecode(LibslImage* image) +bool _stdcall LibslDecode(LibslImage* image) { opj_dparameters dparameters; @@ -218,27 +303,27 @@ bool _STDCALL LibslDecode(LibslImage* image) { opj_set_default_decoder_parameters(&dparameters); dinfo_wrapper dinfo(CODEC_J2K); - opj_setup_decoder(dinfo.dinfo, &dparameters); - cio_wrapper cio(dinfo.dinfo,image->encoded,image->length); - image_wrapper cimage(dinfo.dinfo, cio.cio); // decode happens here - - int dataIndex = 0, compIndex = 0, x, y, c; - image->width = cimage.image->x1 - cimage.image->x0; - image->height = cimage.image->y1 - cimage.image->y0; - image->components = cimage.image->numcomps; - image->decoded = new unsigned char[image->width*image->height*image->components]; - - for (y = 0; y < image->height; y++) - { - for (x = 0; x < image->width; x++) - { - for (c = 0; c < image->components; c++) - image->decoded[dataIndex++] = cimage.image->comps[c].data[compIndex]; - - compIndex++; - } - } - + opj_setup_decoder(dinfo.dinfo, &dparameters); + cio_wrapper cio(dinfo.dinfo,image->encoded,image->length); + image_wrapper cimage(dinfo.dinfo, cio.cio); // decode happens here + + int dataIndex = 0, compIndex = 0, x, y, c; + image->width = cimage.image->x1 - cimage.image->x0; + image->height = cimage.image->y1 - cimage.image->y0; + image->components = cimage.image->numcomps; + image->decoded = new unsigned char[image->width*image->height*image->components]; + + for (y = 0; y < image->height; y++) + { + for (x = 0; x < image->width; x++) + { + for (c = 0; c < image->components; c++) + image->decoded[dataIndex++] = cimage.image->comps[c].data[compIndex]; + + compIndex++; + } + } + return true; } diff --git a/openjpeg-libsl/libsl/libsl.h b/openjpeg-libsl/libsl/libsl.h index 8efbf1bb..fc76a0f8 100644 --- a/openjpeg-libsl/libsl/libsl.h +++ b/openjpeg-libsl/libsl/libsl.h @@ -15,20 +15,15 @@ struct LibslImage int components; }; -#ifdef WIN32 #define DLLEXPORT extern "C" __declspec(dllexport) -#define _STDCALL -#else -#define DLLEXPORT -#define _STDCALL -#endif // uncompresed images are raw RGBA 8bit/channel -DLLEXPORT bool _STDCALL LibslEncode(LibslImage* image); -DLLEXPORT bool _STDCALL LibslDecode(LibslImage* image); -DLLEXPORT bool _STDCALL LibslAllocEncoded(LibslImage* image); -DLLEXPORT bool _STDCALL LibslAllocDecoded(LibslImage* image); -DLLEXPORT void _STDCALL LibslFree(LibslImage* image); +DLLEXPORT bool _stdcall LibslEncode(LibslImage* image, bool lossless); +DLLEXPORT bool _stdcall LibslEncodeBake(LibslImage* image); +DLLEXPORT bool _stdcall LibslDecode(LibslImage* image); +DLLEXPORT bool _stdcall LibslAllocEncoded(LibslImage* image); +DLLEXPORT bool _stdcall LibslAllocDecoded(LibslImage* image); +DLLEXPORT void _stdcall LibslFree(LibslImage* image); #endif diff --git a/openjpegnet/OpenJPEG.cs b/openjpegnet/OpenJPEG.cs index 67e926b6..40ff372d 100644 --- a/openjpegnet/OpenJPEG.cs +++ b/openjpegnet/OpenJPEG.cs @@ -35,14 +35,18 @@ namespace OpenJPEGNet // encode raw to jpeg2000 [DllImport("openjpeg-libsl.dll")] - private static extern bool LibslEncode(ref LibslImage image); + private static extern bool LibslEncode(ref LibslImage image, bool lossless); + + // encode raw to compressed five component jpeg2000 with LL_RGBHM comment + [DllImport("openjpeg-libsl.dll")] + private static extern bool LibslEncodeBake(ref LibslImage image); // decode jpeg2000 to raw [DllImport("openjpeg-libsl.dll")] private static extern bool LibslDecode(ref LibslImage image); // encode - public static byte[] Encode(byte[] decoded, int width, int height, int components) + public static byte[] Encode(byte[] decoded, int width, int height, int components, bool lossless) { if (decoded.Length != width * height * components) throw new ArgumentException("Length of decoded buffer does not match parameters"); @@ -57,7 +61,10 @@ namespace OpenJPEGNet Marshal.Copy(decoded, 0, image.decoded, width * height * components); // codec will allocate output buffer - LibslEncode(ref image); + if (components == 5) + LibslEncodeBake(ref image); + else + LibslEncode(ref image, lossless); // copy output buffer byte[] encoded = new byte[image.length]; @@ -131,9 +138,9 @@ namespace OpenJPEGNet case 5: for (int i = 0; i < (width * height); i++) { - tga[di++] = decoded[si + 2]; // blue + tga[di++] = decoded[si + 2]; // red tga[di++] = decoded[si + 1]; // green - tga[di++] = decoded[si + 0]; // red + tga[di++] = decoded[si + 0]; // blue tga[di++] = decoded[si + 4]; // alpha si += 5; } @@ -141,9 +148,9 @@ namespace OpenJPEGNet case 4: for (int i = 0; i < (width * height); i++) { - tga[di++] = decoded[si + 2]; // blue + tga[di++] = decoded[si + 2]; // red tga[di++] = decoded[si + 1]; // green - tga[di++] = decoded[si + 0]; // red + tga[di++] = decoded[si + 0]; // blue tga[di++] = decoded[si + 3]; // alpha si += 4; } @@ -151,14 +158,23 @@ namespace OpenJPEGNet case 3: for (int i = 0; i < (width * height); i++) { - tga[di++] = decoded[si + 2]; // blue + tga[di++] = decoded[si + 2]; // red tga[di++] = decoded[si + 1]; // green - tga[di++] = decoded[si + 0]; // red + tga[di++] = decoded[si + 0]; // blue tga[di++] = 0xFF; // alpha si += 3; } break; - + case 2: + for (int i = 0; i < (width * height); i++) + { + tga[di++] = decoded[si + 0]; // red + tga[di++] = decoded[si + 0]; // green + tga[di++] = decoded[si + 0]; // blue + tga[di++] = decoded[si + 1]; // alpha + si += 2; + } + break; case 1: for (int i = 0; i < (width * height); i++) { @@ -182,7 +198,7 @@ namespace OpenJPEGNet return LoadTGAClass.LoadTGA(new MemoryStream(DecodeToTGA(encoded))); } - public unsafe static byte[] EncodeFromImage(Bitmap bitmap, string comment) + public unsafe static byte[] EncodeFromImage(Bitmap bitmap, bool lossless) { int numcomps; BitmapData bd; @@ -258,7 +274,7 @@ namespace OpenJPEGNet } bitmap.UnlockBits(bd); - byte[] encoded = Encode(decoded, bitmap.Width, bitmap.Height, numcomps); + byte[] encoded = Encode(decoded, bitmap.Width, bitmap.Height, numcomps, lossless); return encoded; }