From adfc24121857a1d8da75906a8d3aa8164708b3a8 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Sun, 8 Jul 2007 04:35:04 +0000 Subject: [PATCH] * Backporting all of the non-networking and non-inventory changes from the jhurliman branch * The new asset and appearance managers sit alongside the old ones, so no code should break just yet although you might have to be more explicit with your namespaces * Adding importprimscript to work in conjunction with qarl's Maya sculptie exporter * Removing the IA_* examples, these should be remade in to TestClient commands git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1275 52acb1d6-8a22-11de-b505-999d5b087335 --- bin/openjpeg-libsl.dll | Bin 208896 -> 208896 bytes importprimscript/importprimscript.cs | 364 +++ .../importprimscript.csproj | 117 +- .../Appearance.cs => AppearanceManager.cs} | 2599 +++++++++-------- .../Assets.cs => AssetManager.cs} | 1706 +++++------ libsecondlife/Helpers.cs | 47 +- .../InventorySystem/InventoryImage.cs | 2 +- .../InventorySystem/InventoryItem.cs | 2 +- .../InventorySystem/InventoryLandmark.cs | 4 +- .../InventorySystem/InventoryManager.cs | 2 +- .../InventorySystem/InventoryNotecard.cs | 2 +- .../InventorySystem/InventoryScript.cs | 2 +- libsecondlife/LLObject.cs | 25 +- libsecondlife/ObjectManager.cs | 170 +- libsecondlife/ParticleSystem.cs | 125 +- libsecondlife/Permissions.cs | 96 + libsecondlife/Prims.cs | 278 +- libsecondlife/SecondLife.cs | 10 +- libsecondlife/Textures.cs | 74 - .../examples/IA_ImageTool/ImageTool.cs | 234 -- .../IA_ImageTool/Properties/AssemblyInfo.cs | 33 - .../examples/IA_LandmarkTool/LandmarkTool.cs | 168 -- .../IA_NotecardTool/IA_NotecardTool.csproj | 54 - .../examples/IA_NotecardTool/MoreSamples.txt | 2 - .../examples/IA_NotecardTool/NotecardTool.cs | 137 - .../Properties/AssemblyInfo.cs | 33 - .../Commands/Inventory/AppearanceCommand.cs | 8 +- .../Commands/Inventory/DumpOutfitCommand.cs | 4 +- .../Commands/Prims/ImportCommand.cs | 53 +- .../examples/groupmanager/frmGroupInfo.cs | 8 +- .../libsecondlife.Tests.csproj | 5 +- .../libsecondlife.Utilities/Inventory.cs | 162 - .../libsecondlife.Utilities.csproj | 3 - libsecondlife/libsecondlife.csproj | 3 + libsecondlife/libsecondlife.sln | 24 +- openjpeg-libsl/libsl/libsl.cpp | 191 +- openjpeg-libsl/libsl/libsl.h | 17 +- openjpegnet/OpenJPEG.cs | 40 +- 38 files changed, 3368 insertions(+), 3436 deletions(-) create mode 100644 importprimscript/importprimscript.cs rename libsecondlife/examples/IA_ImageTool/IA_ImageTool.csproj => importprimscript/importprimscript.csproj (77%) rename libsecondlife/{libsecondlife.Utilities/Appearance.cs => AppearanceManager.cs} (87%) rename libsecondlife/{libsecondlife.Utilities/Assets.cs => AssetManager.cs} (82%) create mode 100644 libsecondlife/Permissions.cs delete mode 100644 libsecondlife/examples/IA_ImageTool/ImageTool.cs delete mode 100644 libsecondlife/examples/IA_ImageTool/Properties/AssemblyInfo.cs delete mode 100644 libsecondlife/examples/IA_LandmarkTool/LandmarkTool.cs delete mode 100644 libsecondlife/examples/IA_NotecardTool/IA_NotecardTool.csproj delete mode 100644 libsecondlife/examples/IA_NotecardTool/MoreSamples.txt delete mode 100644 libsecondlife/examples/IA_NotecardTool/NotecardTool.cs delete mode 100644 libsecondlife/examples/IA_NotecardTool/Properties/AssemblyInfo.cs delete mode 100644 libsecondlife/libsecondlife.Utilities/Inventory.cs diff --git a/bin/openjpeg-libsl.dll b/bin/openjpeg-libsl.dll index 2076bc5818a20cdfc5000df1c27c27d0fbcaf04e..3134711f5f4ae6aba054329f57776f3b40f58a36 100644 GIT binary patch delta 23113 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 delta 22295 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 H<3;^HyaUR_ 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; }