diff --git a/OpenMetaverse/AssetManager.cs b/OpenMetaverse/AssetManager.cs index 063c8c46..b000040c 100644 --- a/OpenMetaverse/AssetManager.cs +++ b/OpenMetaverse/AssetManager.cs @@ -254,6 +254,11 @@ namespace OpenMetaverse public Simulator Simulator; internal AutoResetEvent HeaderReceivedEvent = new AutoResetEvent(false); + + public AssetDownload() + : base() + { + } } public class XferDownload : Transfer @@ -263,6 +268,11 @@ namespace OpenMetaverse public AssetType Type; public uint PacketNum; public string Filename = String.Empty; + + public XferDownload() + : base() + { + } } /// @@ -281,6 +291,11 @@ namespace OpenMetaverse internal int InitialDataSize; internal AutoResetEvent HeaderReceivedEvent = new AutoResetEvent(false); + + public ImageDownload() + : base() + { + } } /// @@ -292,7 +307,13 @@ namespace OpenMetaverse public AssetType Type; public ulong XferID; public uint PacketNum; + + public AssetUpload() + : base() + { + } } + public class ImageRequest { public ImageRequest(UUID imageid, ImageType type, float priority, int discardLevel) @@ -900,6 +921,9 @@ namespace OpenMetaverse case AssetType.Animation: asset = new AssetAnimation(); break; + case AssetType.Sound: + asset = new AssetSound(); + break; default: Logger.Log("Unimplemented asset type: " + type, Helpers.LogLevel.Error, Client); return null; @@ -911,9 +935,16 @@ namespace OpenMetaverse private Asset WrapAsset(AssetDownload download) { Asset asset = CreateAssetWrapper(download.AssetType); - asset.AssetID = download.AssetID; - asset.AssetData = download.AssetData; - return asset; + if (asset != null) + { + asset.AssetID = download.AssetID; + asset.AssetData = download.AssetData; + return asset; + } + else + { + return null; + } } private void SendNextUploadPacket(AssetUpload upload) @@ -1091,9 +1122,18 @@ namespace OpenMetaverse // 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, download.AssetData, 1000 * asset.TransferData.Packet, - asset.TransferData.Data.Length); - download.Transferred += asset.TransferData.Data.Length; + try + { + Buffer.BlockCopy(asset.TransferData.Data, 0, download.AssetData, 1000 * asset.TransferData.Packet, + asset.TransferData.Data.Length); + download.Transferred += asset.TransferData.Data.Length; + } + catch (ArgumentException) + { + Logger.Log(String.Format("TransferPacket handling failed. TransferData.Data.Length={0}, AssetData.Length={1}, TransferData.Packet={2}", + asset.TransferData.Data.Length, download.AssetData.Length, asset.TransferData.Packet), Helpers.LogLevel.Error); + return; + } //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, diff --git a/OpenMetaverse/TextureCache.cs b/OpenMetaverse/TextureCache.cs index a543cd8b..207c3784 100644 --- a/OpenMetaverse/TextureCache.cs +++ b/OpenMetaverse/TextureCache.cs @@ -213,10 +213,9 @@ namespace OpenMetaverse public bool HasImage(UUID imageID) { if (!Operational()) - { return false; - } - return File.Exists(FileName(imageID)); + else + return File.Exists(FileName(imageID)); } /// diff --git a/OpenMetaverse/TexturePipeline.cs b/OpenMetaverse/TexturePipeline.cs index f82b7abc..d7c5ebb7 100644 --- a/OpenMetaverse/TexturePipeline.cs +++ b/OpenMetaverse/TexturePipeline.cs @@ -57,19 +57,14 @@ namespace OpenMetaverse /// Fired when some texture data is received public event DownloadProgressCallback OnDownloadProgress; - /// For keeping track of active threads available/downloading - /// textures - public int[] ThreadpoolSlots - { - get { lock (threadpoolSlots) { return threadpoolSlots; } } - set { lock (threadpoolSlots) { threadpoolSlots = value; } } - } + public int CurrentCount { get { return currentRequests.Count; } } + public int QueuedCount { get { return requestQueue.Count; } } GridClient client; /// Maximum concurrent texture requests int maxTextureRequests; /// Queue for image requests that have not been sent out yet - Queue> requestQueue; + List requestQueue; /// Current texture downloads Dictionary currentRequests; /// Storage for completed texture downloads @@ -78,6 +73,7 @@ namespace OpenMetaverse int[] threadpoolSlots; Thread downloadMaster; bool running; + object syncObject = new object(); /// /// Default constructor @@ -90,7 +86,7 @@ namespace OpenMetaverse this.client = client; maxTextureRequests = maxRequests; - requestQueue = new Queue>(); + requestQueue = new List(); currentRequests = new Dictionary(maxTextureRequests); completedDownloads = new Dictionary(); resetEvents = new AutoResetEvent[maxTextureRequests]; @@ -133,11 +129,11 @@ namespace OpenMetaverse /// Type of the requested texture public void RequestTexture(UUID textureID, ImageType type) { - if (client.Assets.Cache.HasImage(textureID)) + lock (syncObject) { - // Add to rendering dictionary - lock (completedDownloads) + if (client.Assets.Cache.HasImage(textureID)) { + // Add to rendering dictionary if (!completedDownloads.ContainsKey(textureID)) { completedDownloads.Add(textureID, client.Assets.Cache.GetCachedImage(textureID)); @@ -151,21 +147,18 @@ namespace OpenMetaverse // This image has already been served up, ignore this request } } - } - else - { - lock (requestQueue) + else { // Make sure the request isn't already queued up - foreach (KeyValuePair kvp in requestQueue) - if (kvp.Key == textureID) + foreach (TaskInfo task in requestQueue) + { + if (task.RequestID == textureID) return; + } // Make sure we aren't already downloading the texture if (!currentRequests.ContainsKey(textureID)) - { - requestQueue.Enqueue(new KeyValuePair(textureID, type)); - } + requestQueue.Add(new TaskInfo(textureID, 0, type)); } } } @@ -177,18 +170,17 @@ namespace OpenMetaverse /// ImageDownload object public ImageDownload GetTextureToRender(UUID textureID) { - ImageDownload renderable = new ImageDownload(); - lock (completedDownloads) + lock (syncObject) { if (completedDownloads.ContainsKey(textureID)) { - renderable = completedDownloads[textureID]; + return completedDownloads[textureID]; } else { Logger.Log("Requested texture data for texture that does not exist in dictionary", Helpers.LogLevel.Warning); + return null; } - return renderable; } } @@ -196,12 +188,35 @@ namespace OpenMetaverse /// Remove no longer necessary texture from dictionary /// /// - public void RemoveFromPipeline(UUID textureID) + public bool RemoveFromPipeline(UUID textureID) { - lock (completedDownloads) + lock (syncObject) + return completedDownloads.Remove(textureID); + } + + public void AbortDownload(UUID textureID) + { + lock (syncObject) { - if (completedDownloads.ContainsKey(textureID)) - completedDownloads.Remove(textureID); + for (int i = 0; i < requestQueue.Count; i++) + { + TaskInfo task = requestQueue[i]; + + if (task.RequestID == textureID) + { + requestQueue.RemoveAt(i); + --i; + } + } + + int current; + if (currentRequests.TryGetValue(textureID, out current)) + { + currentRequests.Remove(textureID); + resetEvents[current].Set(); + + // FIXME: Send an abort packet + } } } @@ -230,14 +245,24 @@ namespace OpenMetaverse if (reqNbr != -1) { - KeyValuePair request; - lock (requestQueue) - request = requestQueue.Dequeue(); + TaskInfo task = null; + lock (syncObject) + { + if (requestQueue.Count > 0) + { + task = requestQueue[0]; + requestQueue.RemoveAt(0); + } + } - Logger.DebugLog(String.Format("Sending Worker thread new download request {0}", reqNbr)); - ThreadPool.QueueUserWorkItem(new WaitCallback(TextureRequestDoWork), new TaskInfo(request.Key, reqNbr, request.Value)); + if (task != null) + { + task.RequestNbr = reqNbr; - continue; + Logger.DebugLog(String.Format("Sending Worker thread new download request {0}", reqNbr)); + ThreadPool.QueueUserWorkItem(TextureRequestDoWork, task); + continue; + } } } @@ -252,7 +277,7 @@ namespace OpenMetaverse { TaskInfo ti = (TaskInfo)threadContext; - lock (currentRequests) + lock (syncObject) { if (currentRequests.ContainsKey(ti.RequestID)) { @@ -271,12 +296,12 @@ namespace OpenMetaverse client.Assets.RequestImage(ti.RequestID, ti.Type); // don't release this worker slot until texture is downloaded or timeout occurs - if (!resetEvents[ti.RequestNbr].WaitOne(30 * 1000, false)) + if (!resetEvents[ti.RequestNbr].WaitOne(45 * 1000, false)) { // Timed out Logger.Log("Worker " + ti.RequestNbr + " Timeout waiting for Texture " + ti.RequestID + " to Download", Helpers.LogLevel.Warning); - lock (currentRequests) + lock (syncObject) currentRequests.Remove(ti.RequestID); if (OnDownloadFinished != null) @@ -292,7 +317,7 @@ namespace OpenMetaverse int requestNbr; bool found; - lock (currentRequests) + lock (syncObject) found = currentRequests.TryGetValue(image.ID, out requestNbr); if (asset != null && found) @@ -300,7 +325,7 @@ namespace OpenMetaverse Logger.DebugLog(String.Format("Worker {0} Downloaded texture {1}", requestNbr, image.ID)); // Free up this slot in the ThreadPool - lock (currentRequests) + lock (syncObject) currentRequests.Remove(image.ID); resetEvents[requestNbr].Set(); @@ -308,7 +333,7 @@ namespace OpenMetaverse if (image.Success) { // Add to the completed texture dictionary - lock (completedDownloads) + lock (syncObject) completedDownloads[image.ID] = image; } else diff --git a/Programs/SimExport/SimExport.cs b/Programs/SimExport/SimExport.cs index 169db8ea..b20fb979 100644 --- a/Programs/SimExport/SimExport.cs +++ b/Programs/SimExport/SimExport.cs @@ -41,7 +41,7 @@ namespace SimExport running = true; client = new GridClient(); - texturePipeline = new TexturePipeline(client); + texturePipeline = new TexturePipeline(client, 10); texturePipeline.OnDownloadFinished += new TexturePipeline.DownloadFinishedCallback(texturePipeline_OnDownloadFinished); //Settings.LOG_LEVEL = Helpers.LogLevel.Info; @@ -345,7 +345,7 @@ namespace SimExport for (int i = 0; i < te.FaceTextures.Length; i++) { if (te.FaceTextures[i] != null && !texturesFinished.ContainsKey(te.FaceTextures[i].TextureID)) - texturePipeline.RequestTexture(te.FaceTextures[i].TextureID); + texturePipeline.RequestTexture(te.FaceTextures[i].TextureID, ImageType.Normal); } } } diff --git a/Programs/SimExport/TexturePipeline.cs b/Programs/SimExport/TexturePipeline.cs deleted file mode 100644 index 5f21fb29..00000000 --- a/Programs/SimExport/TexturePipeline.cs +++ /dev/null @@ -1,346 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using OpenMetaverse; - -/* - * the onnewprim function will add missing texture uuids to the download queue, - * and a separate thread will pull entries off that queue. - * if they exist in the cache it will add that texture to a dictionary that the rendering loop accesses, - * otherwise it will start the download. - * the ondownloaded function will put the new texture in the same dictionary - * - * - * Easy Start: - * subscribe to OnImageRenderReady event - * send request with RequestTexture() - * - * when OnImageRenderReady fires: - * request image data with GetTextureToRender() using key returned in OnImageRenderReady event - * (optionally) use RemoveFromPipeline() with key to cleanup dictionary - */ - -namespace SimExport -{ - class TaskInfo - { - public UUID RequestID; - public int RequestNbr; - - - public TaskInfo(UUID reqID, int reqNbr) - { - RequestID = reqID; - RequestNbr = reqNbr; - } - } - - /// - /// Texture request download handler, allows a configurable number of download slots - /// - public class TexturePipeline - { - private static GridClient Client; - - // queue for requested images - private Queue RequestQueue; - - // list of current requests in process - private Dictionary CurrentRequests; - - private static AutoResetEvent[] resetEvents; - - private static int[] threadpoolSlots; - - /// - /// For keeping track of active threads available/downloading textures - /// - public static int[] ThreadpoolSlots - { - get { lock (threadpoolSlots) { return threadpoolSlots; } } - set { lock (threadpoolSlots) { threadpoolSlots = value; } } - } - - public int QueuedCount { get { return RequestQueue.Count; } } - public int CurrentCount { get { return CurrentRequests.Count; } } - - // storage for images ready to render - private Dictionary RenderReady; - - // maximum allowed concurrent requests at once - const int MAX_TEXTURE_REQUESTS = 10; - - /// - /// - /// - /// - /// - public delegate void DownloadFinishedCallback(UUID id, bool success); - /// - /// - /// - /// - /// - /// - public delegate void DownloadProgressCallback(UUID image, int recieved, int total); - - /// Fired when a texture download completes - public event DownloadFinishedCallback OnDownloadFinished; - /// - public event DownloadProgressCallback OnDownloadProgress; - - private Thread downloadMaster; - private bool Running; - - private AssetManager.ImageReceivedCallback DownloadCallback; - private AssetManager.ImageReceiveProgressCallback DownloadProgCallback; - - /// - /// Default constructor - /// - /// Reference to SecondLife client - public TexturePipeline(GridClient client) - { - Running = true; - - RequestQueue = new Queue(); - CurrentRequests = new Dictionary(MAX_TEXTURE_REQUESTS); - - RenderReady = new Dictionary(); - - resetEvents = new AutoResetEvent[MAX_TEXTURE_REQUESTS]; - threadpoolSlots = new int[MAX_TEXTURE_REQUESTS]; - - // pre-configure autoreset events/download slots - for (int i = 0; i < MAX_TEXTURE_REQUESTS; i++) - { - resetEvents[i] = new AutoResetEvent(false); - threadpoolSlots[i] = -1; - } - - Client = client; - - DownloadCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); - DownloadProgCallback = new AssetManager.ImageReceiveProgressCallback(Assets_OnImageReceiveProgress); - Client.Assets.OnImageReceived += DownloadCallback; - Client.Assets.OnImageReceiveProgress += DownloadProgCallback; - - // Fire up the texture download thread - downloadMaster = new Thread(new ThreadStart(DownloadThread)); - downloadMaster.Start(); - } - - public void Shutdown() - { - Client.Assets.OnImageReceived -= DownloadCallback; - Client.Assets.OnImageReceiveProgress -= DownloadProgCallback; - - RequestQueue.Clear(); - - for (int i = 0; i < resetEvents.Length; i++) - if (resetEvents[i] != null) - resetEvents[i].Set(); - - Running = false; - } - - /// - /// Request a texture be downloaded, once downloaded OnImageRenderReady event will be fired - /// containing texture key which can be used to retrieve texture with GetTextureToRender method - /// - /// id of Texture to request - public void RequestTexture(UUID textureID) - { - if (Client.Assets.Cache.HasImage(textureID)) - { - // Add to rendering dictionary - lock (RenderReady) - { - if (!RenderReady.ContainsKey(textureID)) - { - RenderReady.Add(textureID, Client.Assets.Cache.GetCachedImage(textureID)); - - // Let any subscribers know about it - if (OnDownloadFinished != null) - { - OnDownloadFinished(textureID, true); - } - } - else - { - // This image has already been served up, ignore this request - } - } - } - else - { - lock (RequestQueue) - { - // Make sure we aren't already downloading the texture - if (!RequestQueue.Contains(textureID) && !CurrentRequests.ContainsKey(textureID)) - { - RequestQueue.Enqueue(textureID); - } - } - } - } - - /// - /// retrieve texture information from dictionary - /// - /// Texture ID - /// ImageDownload object - public ImageDownload GetTextureToRender(UUID textureID) - { - ImageDownload renderable = new ImageDownload(); - lock (RenderReady) - { - if (RenderReady.ContainsKey(textureID)) - { - renderable = RenderReady[textureID]; - } - else - { - Logger.Log("Requested texture data for texture that does not exist in dictionary", Helpers.LogLevel.Warning); - } - return renderable; - } - } - - /// - /// Remove no longer necessary texture from dictionary - /// - /// - public void RemoveFromPipeline(UUID textureID) - { - lock (RenderReady) - { - if (RenderReady.ContainsKey(textureID)) - RenderReady.Remove(textureID); - } - } - - /// - /// Master Download Thread, Queues up downloads in the threadpool - /// - private void DownloadThread() - { - int reqNbr; - - while (Running) - { - if (RequestQueue.Count > 0) - { - reqNbr = -1; - // find available slot for reset event - for (int i = 0; i < threadpoolSlots.Length; i++) - { - if (threadpoolSlots[i] == -1) - { - threadpoolSlots[i] = 1; - reqNbr = i; - break; - } - } - - if (reqNbr != -1) - { - UUID requestID; - lock (RequestQueue) - requestID = RequestQueue.Dequeue(); - - Logger.DebugLog(String.Format("Sending Worker thread new download request {0}", reqNbr)); - ThreadPool.QueueUserWorkItem(new WaitCallback(textureRequestDoWork), new TaskInfo(requestID, reqNbr)); - - continue; - } - } - - // Queue was empty, let's give up some CPU time - Thread.Sleep(500); - } - } - - void textureRequestDoWork(Object threadContext) - { - TaskInfo ti = (TaskInfo)threadContext; - - lock (CurrentRequests) - { - if (CurrentRequests.ContainsKey(ti.RequestID)) - { - threadpoolSlots[ti.RequestNbr] = -1; - return; - } - else - { - CurrentRequests.Add(ti.RequestID, ti.RequestNbr); - } - } - - Logger.DebugLog(String.Format("Worker {0} Requesting {1}", ti.RequestNbr, ti.RequestID)); - - resetEvents[ti.RequestNbr].Reset(); - Client.Assets.RequestImage(ti.RequestID, ImageType.Normal); - - // don't release this worker slot until texture is downloaded or timeout occurs - if (!resetEvents[ti.RequestNbr].WaitOne(30 * 1000, false)) - { - // Timed out - Logger.Log("Worker " + ti.RequestNbr + " Timeout waiting for Texture " + ti.RequestID + " to Download", Helpers.LogLevel.Warning); - - lock (CurrentRequests) - CurrentRequests.Remove(ti.RequestID); - } - - // free up this download slot - threadpoolSlots[ti.RequestNbr] = -1; - } - - private void Assets_OnImageReceived(ImageDownload image, AssetTexture asset) - { - // Free up this slot in the ThreadPool - lock (CurrentRequests) - { - int requestNbr; - if (asset != null && CurrentRequests.TryGetValue(image.ID, out requestNbr)) - { - Logger.DebugLog(String.Format("Worker {0} Downloaded texture {1}", requestNbr, image.ID)); - resetEvents[requestNbr].Set(); - CurrentRequests.Remove(image.ID); - } - } - - if (image.Success) - { - lock (RenderReady) - { - if (!RenderReady.ContainsKey(image.ID)) - { - // Add to rendering dictionary - RenderReady.Add(image.ID, image); - } - } - } - else - { - Console.WriteLine(String.Format("Download of texture {0} failed. NotFound={1}", image.ID, image.NotFound)); - } - - // Let any subscribers know about it - if (OnDownloadFinished != null) - { - OnDownloadFinished(image.ID, image.Success); - } - } - - private void Assets_OnImageReceiveProgress(UUID image, int lastPacket, int recieved, int total) - { - if (OnDownloadProgress != null) - { - OnDownloadProgress(image, recieved, total); - } - } - } -} diff --git a/Programs/Simian/Extensions/AssetManager.cs b/Programs/Simian/Extensions/AssetManager.cs index cd23eb5e..453ad841 100644 --- a/Programs/Simian/Extensions/AssetManager.cs +++ b/Programs/Simian/Extensions/AssetManager.cs @@ -4,7 +4,6 @@ using System.IO; using ExtensionLoader; using OpenMetaverse; using OpenMetaverse.Imaging; -using OpenMetaverse.Packets; namespace Simian.Extensions { @@ -14,7 +13,6 @@ namespace Simian.Extensions Simian server; Dictionary AssetStore = new Dictionary(); - Dictionary CurrentUploads = new Dictionary(); string UploadDir; public AssetManager() @@ -41,11 +39,6 @@ namespace Simian.Extensions LoadAssets(server.DataDir); LoadAssets(UploadDir); - - server.UDP.RegisterPacketCallback(PacketType.AssetUploadRequest, new PacketCallback(AssetUploadRequestHandler)); - server.UDP.RegisterPacketCallback(PacketType.SendXferPacket, new PacketCallback(SendXferPacketHandler)); - server.UDP.RegisterPacketCallback(PacketType.AbortXfer, new PacketCallback(AbortXferHandler)); - server.UDP.RegisterPacketCallback(PacketType.TransferRequest, new PacketCallback(TransferRequestHandler)); } public void Stop() @@ -93,290 +86,6 @@ namespace Simian.Extensions return AssetStore.TryGetValue(id, out asset); } - #region Xfer System - - void AssetUploadRequestHandler(Packet packet, Agent agent) - { - AssetUploadRequestPacket request = (AssetUploadRequestPacket)packet; - UUID assetID = UUID.Combine(request.AssetBlock.TransactionID, agent.SecureSessionID); - - // Check if the asset is small enough to fit in a single packet - if (request.AssetBlock.AssetData.Length != 0) - { - // Create a new asset from the completed upload - Asset asset = CreateAsset((AssetType)request.AssetBlock.Type, assetID, request.AssetBlock.AssetData); - if (asset == null) - { - Logger.Log("Failed to create asset from uploaded data", Helpers.LogLevel.Warning); - return; - } - - Logger.DebugLog(String.Format("Storing uploaded asset {0} ({1})", assetID, asset.AssetType)); - - asset.Temporary = (request.AssetBlock.Tempfile | request.AssetBlock.StoreLocal); - - // Store the asset - StoreAsset(asset); - - // Send a success response - AssetUploadCompletePacket complete = new AssetUploadCompletePacket(); - complete.AssetBlock.Success = true; - complete.AssetBlock.Type = request.AssetBlock.Type; - complete.AssetBlock.UUID = assetID; - server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Inventory); - } - else - { - // Create a new (empty) asset for the upload - Asset asset = CreateAsset((AssetType)request.AssetBlock.Type, assetID, null); - if (asset == null) - { - Logger.Log("Failed to create asset from uploaded data", Helpers.LogLevel.Warning); - return; - } - - Logger.DebugLog(String.Format("Starting upload for {0} ({1})", assetID, asset.AssetType)); - - asset.Temporary = (request.AssetBlock.Tempfile | request.AssetBlock.StoreLocal); - - RequestXferPacket xfer = new RequestXferPacket(); - xfer.XferID.DeleteOnCompletion = request.AssetBlock.Tempfile; - xfer.XferID.FilePath = 0; - xfer.XferID.Filename = new byte[0]; - xfer.XferID.ID = request.AssetBlock.TransactionID.GetULong(); - xfer.XferID.UseBigPackets = false; - xfer.XferID.VFileID = asset.AssetID; - xfer.XferID.VFileType = request.AssetBlock.Type; - - // Add this asset to the current upload list - lock (CurrentUploads) - CurrentUploads[xfer.XferID.ID] = asset; - - server.UDP.SendPacket(agent.AgentID, xfer, PacketCategory.Inventory); - } - } - - void SendXferPacketHandler(Packet packet, Agent agent) - { - SendXferPacketPacket xfer = (SendXferPacketPacket)packet; - - Asset asset; - if (CurrentUploads.TryGetValue(xfer.XferID.ID, out asset)) - { - if (asset.AssetData == null) - { - if (xfer.XferID.Packet != 0) - { - Logger.Log(String.Format("Received Xfer packet {0} before the first packet!", - xfer.XferID.Packet), Helpers.LogLevel.Error); - return; - } - - uint size = Utils.BytesToUInt(xfer.DataPacket.Data); - asset.AssetData = new byte[size]; - - Buffer.BlockCopy(xfer.DataPacket.Data, 4, asset.AssetData, 0, xfer.DataPacket.Data.Length - 4); - - // Confirm the first upload packet - ConfirmXferPacketPacket confirm = new ConfirmXferPacketPacket(); - confirm.XferID.ID = xfer.XferID.ID; - confirm.XferID.Packet = xfer.XferID.Packet; - server.UDP.SendPacket(agent.AgentID, confirm, PacketCategory.Asset); - } - else - { - Buffer.BlockCopy(xfer.DataPacket.Data, 0, asset.AssetData, (int)xfer.XferID.Packet * 1000, - xfer.DataPacket.Data.Length); - - // Confirm this upload packet - ConfirmXferPacketPacket confirm = new ConfirmXferPacketPacket(); - confirm.XferID.ID = xfer.XferID.ID; - confirm.XferID.Packet = xfer.XferID.Packet; - server.UDP.SendPacket(agent.AgentID, confirm, PacketCategory.Asset); - - if ((xfer.XferID.Packet & (uint)0x80000000) != 0) - { - // Asset upload finished - Logger.DebugLog(String.Format("Completed Xfer upload of asset {0} ({1}", asset.AssetID, asset.AssetType)); - - lock (CurrentUploads) - CurrentUploads.Remove(xfer.XferID.ID); - - StoreAsset(asset); - - AssetUploadCompletePacket complete = new AssetUploadCompletePacket(); - complete.AssetBlock.Success = true; - complete.AssetBlock.Type = (sbyte)asset.AssetType; - complete.AssetBlock.UUID = asset.AssetID; - server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Asset); - } - } - } - else - { - Logger.DebugLog("Received a SendXferPacket for an unknown upload"); - } - } - - void AbortXferHandler(Packet packet, Agent agent) - { - AbortXferPacket abort = (AbortXferPacket)packet; - - lock (CurrentUploads) - { - if (CurrentUploads.ContainsKey(abort.XferID.ID)) - { - Logger.DebugLog(String.Format("Aborting Xfer {0}, result: {1}", abort.XferID.ID, - (TransferError)abort.XferID.Result)); - - CurrentUploads.Remove(abort.XferID.ID); - } - else - { - Logger.DebugLog(String.Format("Received an AbortXfer for an unknown xfer {0}", - abort.XferID.ID)); - } - } - } - - #endregion Xfer System - - #region Transfer System - - void TransferRequestHandler(Packet packet, Agent agent) - { - TransferRequestPacket request = (TransferRequestPacket)packet; - - ChannelType channel = (ChannelType)request.TransferInfo.ChannelType; - SourceType source = (SourceType)request.TransferInfo.SourceType; - - if (channel == ChannelType.Asset) - { - // Construct the response packet - TransferInfoPacket response = new TransferInfoPacket(); - response.TransferInfo = new TransferInfoPacket.TransferInfoBlock(); - response.TransferInfo.TransferID = request.TransferInfo.TransferID; - - if (source == SourceType.Asset) - { - // Parse the request - UUID assetID = new UUID(request.TransferInfo.Params, 0); - AssetType type = (AssetType)(sbyte)Utils.BytesToInt(request.TransferInfo.Params, 16); - - // Set the response channel type - response.TransferInfo.ChannelType = (int)ChannelType.Asset; - - // Params - response.TransferInfo.Params = new byte[20]; - Buffer.BlockCopy(assetID.GetBytes(), 0, response.TransferInfo.Params, 0, 16); - Buffer.BlockCopy(Utils.IntToBytes((int)type), 0, response.TransferInfo.Params, 16, 4); - - // Check if we have this asset - Asset asset; - if (AssetStore.TryGetValue(assetID, out asset)) - { - if (asset.AssetType == type) - { - Logger.DebugLog(String.Format("Transferring asset {0} ({1})", asset.AssetID, asset.AssetType)); - - // Asset found - response.TransferInfo.Size = asset.AssetData.Length; - response.TransferInfo.Status = (int)StatusCode.OK; - response.TransferInfo.TargetType = (int)TargetType.Unknown; // Doesn't seem to be used by the client - - server.UDP.SendPacket(agent.AgentID, response, PacketCategory.Asset); - - // Transfer system does not wait for ACKs, just sends all of the - // packets for this transfer out - const int MAX_CHUNK_SIZE = Settings.MAX_PACKET_SIZE - 100; - int processedLength = 0; - int packetNum = 0; - while (processedLength < asset.AssetData.Length) - { - TransferPacketPacket transfer = new TransferPacketPacket(); - transfer.TransferData.ChannelType = (int)ChannelType.Asset; - transfer.TransferData.TransferID = request.TransferInfo.TransferID; - transfer.TransferData.Packet = packetNum++; - - int chunkSize = Math.Min(asset.AssetData.Length - processedLength, MAX_CHUNK_SIZE); - transfer.TransferData.Data = new byte[chunkSize]; - Buffer.BlockCopy(asset.AssetData, processedLength, transfer.TransferData.Data, 0, chunkSize); - processedLength += chunkSize; - - if (processedLength >= asset.AssetData.Length) - transfer.TransferData.Status = (int)StatusCode.Done; - else - transfer.TransferData.Status = (int)StatusCode.OK; - - server.UDP.SendPacket(agent.AgentID, transfer, PacketCategory.Asset); - } - } - else - { - Logger.Log(String.Format( - "Request for asset {0} with type {1} does not match actual asset type {2}", - assetID, type, asset.AssetType), Helpers.LogLevel.Warning); - } - } - else - { - Logger.Log(String.Format("Request for missing asset {0} with type {1}", - assetID, type), Helpers.LogLevel.Warning); - - // Asset not found - response.TransferInfo.Size = 0; - response.TransferInfo.Status = (int)StatusCode.UnknownSource; - response.TransferInfo.TargetType = (int)TargetType.Unknown; - - server.UDP.SendPacket(agent.AgentID, response, PacketCategory.Asset); - } - } - else if (source == SourceType.SimEstate) - { - UUID agentID = new UUID(request.TransferInfo.Params, 0); - UUID sessionID = new UUID(request.TransferInfo.Params, 16); - EstateAssetType type = (EstateAssetType)Utils.BytesToInt(request.TransferInfo.Params, 32); - - Logger.Log("Please implement estate asset transfers", Helpers.LogLevel.Warning); - } - else if (source == SourceType.SimInventoryItem) - { - UUID agentID = new UUID(request.TransferInfo.Params, 0); - UUID sessionID = new UUID(request.TransferInfo.Params, 16); - UUID ownerID = new UUID(request.TransferInfo.Params, 32); - UUID taskID = new UUID(request.TransferInfo.Params, 48); - UUID itemID = new UUID(request.TransferInfo.Params, 64); - UUID assetID = new UUID(request.TransferInfo.Params, 80); - AssetType type = (AssetType)(sbyte)Utils.BytesToInt(request.TransferInfo.Params, 96); - - if (taskID != UUID.Zero) - { - // Task (prim) inventory request - Logger.Log("Please implement task inventory transfers", Helpers.LogLevel.Warning); - } - else - { - // Agent inventory request - Logger.Log("Please implement agent inventory transfer", Helpers.LogLevel.Warning); - } - } - else - { - Logger.Log(String.Format( - "Received a TransferRequest that we don't know how to handle. Channel: {0}, Source: {1}", - channel, source), Helpers.LogLevel.Warning); - } - } - else - { - Logger.Log(String.Format( - "Received a TransferRequest that we don't know how to handle. Channel: {0}, Source: {1}", - channel, source), Helpers.LogLevel.Warning); - } - } - - #endregion Transfer System - void SaveAsset(Asset asset) { try diff --git a/Programs/Simian/Extensions/AvatarManager.cs b/Programs/Simian/Extensions/AvatarManager.cs index 83464ae4..7bad13e9 100644 --- a/Programs/Simian/Extensions/AvatarManager.cs +++ b/Programs/Simian/Extensions/AvatarManager.cs @@ -23,15 +23,15 @@ namespace Simian.Extensions { this.server = server; - server.UDP.RegisterPacketCallback(PacketType.AvatarPropertiesRequest, new PacketCallback(AvatarPropertiesRequestHandler)); - server.UDP.RegisterPacketCallback(PacketType.AgentWearablesRequest, new PacketCallback(AgentWearablesRequestHandler)); - server.UDP.RegisterPacketCallback(PacketType.AgentIsNowWearing, new PacketCallback(AgentIsNowWearingHandler)); - server.UDP.RegisterPacketCallback(PacketType.AgentSetAppearance, new PacketCallback(AgentSetAppearanceHandler)); - server.UDP.RegisterPacketCallback(PacketType.AgentCachedTexture, new PacketCallback(AgentCachedTextureHandler)); - server.UDP.RegisterPacketCallback(PacketType.AgentAnimation, new PacketCallback(AgentAnimationHandler)); - server.UDP.RegisterPacketCallback(PacketType.SoundTrigger, new PacketCallback(SoundTriggerHandler)); - server.UDP.RegisterPacketCallback(PacketType.ViewerEffect, new PacketCallback(ViewerEffectHandler)); - server.UDP.RegisterPacketCallback(PacketType.UUIDNameRequest, new PacketCallback(UUIDNameRequestHandler)); + server.UDP.RegisterPacketCallback(PacketType.AvatarPropertiesRequest, AvatarPropertiesRequestHandler); + server.UDP.RegisterPacketCallback(PacketType.AgentWearablesRequest, AgentWearablesRequestHandler); + server.UDP.RegisterPacketCallback(PacketType.AgentIsNowWearing, AgentIsNowWearingHandler); + server.UDP.RegisterPacketCallback(PacketType.AgentSetAppearance, AgentSetAppearanceHandler); + server.UDP.RegisterPacketCallback(PacketType.AgentCachedTexture, AgentCachedTextureHandler); + server.UDP.RegisterPacketCallback(PacketType.AgentAnimation, AgentAnimationHandler); + server.UDP.RegisterPacketCallback(PacketType.SoundTrigger, SoundTriggerHandler); + server.UDP.RegisterPacketCallback(PacketType.ViewerEffect, ViewerEffectHandler); + server.UDP.RegisterPacketCallback(PacketType.UUIDNameRequest, UUIDNameRequestHandler); if (CoarseLocationTimer != null) CoarseLocationTimer.Dispose(); CoarseLocationTimer = new Timer(CoarseLocationTimer_Elapsed); diff --git a/Programs/Simian/Extensions/Periscope.cs b/Programs/Simian/Extensions/Periscope.cs index ed8f99c5..6c525c33 100644 --- a/Programs/Simian/Extensions/Periscope.cs +++ b/Programs/Simian/Extensions/Periscope.cs @@ -15,6 +15,7 @@ namespace Simian.Extensions GridClient client; PeriscopeImageDelivery imageDelivery; PeriscopeMovement movement; + PeriscopeTransferManager transferManager; object loginLock = new object(); public Periscope() @@ -30,13 +31,13 @@ namespace Simian.Extensions client.Settings.MULTIPLE_SIMS = false; client.Settings.SEND_AGENT_UPDATES = false; - client.Network.OnCurrentSimChanged += new NetworkManager.CurrentSimChangedCallback(Network_OnCurrentSimChanged); - client.Objects.OnNewPrim += new OpenMetaverse.ObjectManager.NewPrimCallback(Objects_OnNewPrim); - client.Objects.OnNewAvatar += new OpenMetaverse.ObjectManager.NewAvatarCallback(Objects_OnNewAvatar); - client.Objects.OnNewAttachment += new OpenMetaverse.ObjectManager.NewAttachmentCallback(Objects_OnNewAttachment); - client.Objects.OnObjectKilled += new OpenMetaverse.ObjectManager.KillObjectCallback(Objects_OnObjectKilled); - client.Objects.OnObjectUpdated += new OpenMetaverse.ObjectManager.ObjectUpdatedCallback(Objects_OnObjectUpdated); - client.Avatars.OnAvatarAppearance += new OpenMetaverse.AvatarManager.AvatarAppearanceCallback(Avatars_OnAvatarAppearance); + client.Network.OnCurrentSimChanged += Network_OnCurrentSimChanged; + client.Objects.OnNewPrim += Objects_OnNewPrim; + client.Objects.OnNewAvatar += Objects_OnNewAvatar; + client.Objects.OnNewAttachment += Objects_OnNewAttachment; + client.Objects.OnObjectKilled += Objects_OnObjectKilled; + client.Objects.OnObjectUpdated += Objects_OnObjectUpdated; + client.Avatars.OnAvatarAppearance += Avatars_OnAvatarAppearance; client.Terrain.OnLandPatch += new TerrainManager.LandPatchCallback(Terrain_OnLandPatch); client.Self.OnChat += new AgentManager.ChatCallback(Self_OnChat); client.Self.OnTeleport += new AgentManager.TeleportCallback(Self_OnTeleport); @@ -45,13 +46,20 @@ namespace Simian.Extensions server.UDP.RegisterPacketCallback(PacketType.AgentUpdate, AgentUpdateHandler); server.UDP.RegisterPacketCallback(PacketType.ChatFromViewer, ChatFromViewerHandler); + server.UDP.RegisterPacketCallback(PacketType.ObjectGrab, ObjectGrabHandler); + server.UDP.RegisterPacketCallback(PacketType.ObjectGrabUpdate, ObjectGrabUpdateHandler); + server.UDP.RegisterPacketCallback(PacketType.ObjectDeGrab, ObjectDeGrabHandler); + server.UDP.RegisterPacketCallback(PacketType.ViewerEffect, ViewerEffectHandler); + server.UDP.RegisterPacketCallback(PacketType.AgentAnimation, AgentAnimationHandler); imageDelivery = new PeriscopeImageDelivery(server, client); movement = new PeriscopeMovement(server, this); + transferManager = new PeriscopeTransferManager(server, client); } public void Stop() { + transferManager.Stop(); movement.Stop(); imageDelivery.Stop(); @@ -62,6 +70,8 @@ namespace Simian.Extensions void Objects_OnNewPrim(Simulator simulator, Primitive prim, ulong regionHandle, ushort timeDilation) { SimulationObject simObj = new SimulationObject(prim, server); + if (MasterAgent != null) + simObj.Prim.OwnerID = MasterAgent.AgentID; server.Scene.ObjectAdd(this, simObj, PrimFlags.None); } @@ -244,7 +254,6 @@ namespace Simian.Extensions switch (messageParts[0]) { case "/teleport": - //string simName; float x, y, z; if (messageParts.Length == 5 && @@ -261,6 +270,40 @@ namespace Simian.Extensions server.Avatars.SendAlert(agent, "Usage: /teleport \"sim name\" x y z"); } return; + case "/stats": + server.Avatars.SendAlert(agent, String.Format("Downloading textures: {0}, Queued textures: {1}", + imageDelivery.Pipeline.CurrentCount, imageDelivery.Pipeline.QueuedCount)); + return; + case "/nudemod": + int count = 0; + Dictionary agents; + lock (server.Agents) + agents = new Dictionary(server.Agents); + + foreach (Agent curAgent in agents.Values) + { + if (curAgent != agent && curAgent.VisualParams != null) + { + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.LowerBaked] = null; + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.LowerJacket] = null; + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.LowerPants] = null; + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.LowerShoes] = null; + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.LowerSocks] = null; + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.LowerUnderpants] = null; + + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.UpperBaked] = null; + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.UpperGloves] = null; + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.UpperJacket] = null; + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.UpperShirt] = null; + curAgent.Avatar.Textures.FaceTextures[(int)AppearanceManager.TextureIndex.UpperUndershirt] = null; + + server.Scene.AvatarAppearance(this, curAgent, curAgent.Avatar.Textures, curAgent.VisualParams); + ++count; + } + } + + server.Avatars.SendAlert(agent, String.Format("Modified appearances for {0} avatar(s)", count)); + return; } } @@ -273,6 +316,12 @@ namespace Simian.Extensions client.Self.Chat(finalMessage, chat.ChatData.Channel, (ChatType)chat.ChatData.Type); } + static void EraseTexture(Avatar avatar, AppearanceManager.TextureIndex texture) + { + Primitive.TextureEntryFace face = avatar.Textures.FaceTextures[(int)texture]; + if (face != null) face.TextureID = UUID.Zero; + } + void AgentUpdateHandler(Packet packet, Agent agent) { AgentUpdatePacket update = (AgentUpdatePacket)packet; @@ -308,13 +357,77 @@ namespace Simian.Extensions if (MasterAgent == null || update.AgentData.AgentID == MasterAgent.AgentID) { - // Forward AgentUpdate packets with the AgentID/SessionID set to the bots ID update.AgentData.AgentID = client.Self.AgentID; update.AgentData.SessionID = client.Self.SessionID; client.Network.SendPacket(update); } } + void ObjectGrabHandler(Packet packet, Agent agent) + { + ObjectGrabPacket grab = (ObjectGrabPacket)packet; + + if (MasterAgent == null || grab.AgentData.AgentID == MasterAgent.AgentID) + { + grab.AgentData.AgentID = client.Self.AgentID; + grab.AgentData.SessionID = client.Self.SessionID; + + client.Network.SendPacket(grab); + } + } + + void ObjectGrabUpdateHandler(Packet packet, Agent agent) + { + ObjectGrabUpdatePacket grabUpdate = (ObjectGrabUpdatePacket)packet; + + if (MasterAgent == null || grabUpdate.AgentData.AgentID == MasterAgent.AgentID) + { + grabUpdate.AgentData.AgentID = client.Self.AgentID; + grabUpdate.AgentData.SessionID = client.Self.SessionID; + + client.Network.SendPacket(grabUpdate); + } + } + + void ObjectDeGrabHandler(Packet packet, Agent agent) + { + ObjectDeGrabPacket degrab = (ObjectDeGrabPacket)packet; + + if (MasterAgent == null || degrab.AgentData.AgentID == MasterAgent.AgentID) + { + degrab.AgentData.AgentID = client.Self.AgentID; + degrab.AgentData.SessionID = client.Self.SessionID; + + client.Network.SendPacket(degrab); + } + } + + void ViewerEffectHandler(Packet packet, Agent agent) + { + ViewerEffectPacket effect = (ViewerEffectPacket)packet; + + if (MasterAgent == null || effect.AgentData.AgentID == MasterAgent.AgentID) + { + effect.AgentData.AgentID = client.Self.AgentID; + effect.AgentData.SessionID = client.Self.SessionID; + + client.Network.SendPacket(effect); + } + } + + void AgentAnimationHandler(Packet packet, Agent agent) + { + AgentAnimationPacket animation = (AgentAnimationPacket)packet; + + if (MasterAgent == null || animation.AgentData.AgentID == MasterAgent.AgentID) + { + animation.AgentData.AgentID = client.Self.AgentID; + animation.AgentData.SessionID = client.Self.SessionID; + + client.Network.SendPacket(animation); + } + } + #endregion Simian client packet handlers } diff --git a/Programs/Simian/Extensions/PeriscopeImageDelivery.cs b/Programs/Simian/Extensions/PeriscopeImageDelivery.cs index e9478c3a..aea97a8c 100644 --- a/Programs/Simian/Extensions/PeriscopeImageDelivery.cs +++ b/Programs/Simian/Extensions/PeriscopeImageDelivery.cs @@ -9,9 +9,10 @@ namespace Simian.Extensions { public class PeriscopeImageDelivery { + public TexturePipeline Pipeline; + Simian server; GridClient client; - TexturePipeline pipeline; Dictionary currentDownloads = new Dictionary(); public PeriscopeImageDelivery(Simian server, GridClient client) @@ -19,15 +20,15 @@ namespace Simian.Extensions this.server = server; this.client = client; - pipeline = new TexturePipeline(client, 10); - pipeline.OnDownloadFinished += new TexturePipeline.DownloadFinishedCallback(pipeline_OnDownloadFinished); + Pipeline = new TexturePipeline(client, 12); + Pipeline.OnDownloadFinished += new TexturePipeline.DownloadFinishedCallback(pipeline_OnDownloadFinished); server.UDP.RegisterPacketCallback(PacketType.RequestImage, RequestImageHandler); } public void Stop() { - pipeline.Shutdown(); + Pipeline.Shutdown(); } void RequestImageHandler(Packet packet, Agent agent) @@ -54,7 +55,8 @@ namespace Simian.Extensions } else if (block.DiscardLevel == -1 && block.DownloadPriority == 0.0f) { - // Aborting a download we are not tracking, ignore + // Aborting a download we are not tracking, this may be in the pipeline + Pipeline.AbortDownload(block.Image); } else { @@ -73,7 +75,7 @@ namespace Simian.Extensions lock (currentDownloads) currentDownloads[block.Image] = download; - pipeline.RequestTexture(block.Image, (ImageType)block.Type); + Pipeline.RequestTexture(block.Image, (ImageType)block.Type); } } } @@ -90,10 +92,10 @@ namespace Simian.Extensions if (success) { // Set the texture to the downloaded texture data - AssetTexture texture = new AssetTexture(id, pipeline.GetTextureToRender(id).AssetData); + AssetTexture texture = new AssetTexture(id, Pipeline.GetTextureToRender(id).AssetData); download.Texture = texture; - pipeline.RemoveFromPipeline(id); + Pipeline.RemoveFromPipeline(id); // Store this texture in the local asset store for later server.Assets.StoreAsset(texture); diff --git a/Programs/Simian/Extensions/PeriscopeTransferManager.cs b/Programs/Simian/Extensions/PeriscopeTransferManager.cs new file mode 100644 index 00000000..b0cf4d2d --- /dev/null +++ b/Programs/Simian/Extensions/PeriscopeTransferManager.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; +using System.IO; +using ExtensionLoader; +using OpenMetaverse; +using OpenMetaverse.Imaging; +using OpenMetaverse.Packets; + +namespace Simian.Extensions +{ + public class PeriscopeTransferManager + { + public const string UPLOAD_DIR = "uploadedAssets"; + + Simian server; + GridClient client; + Dictionary CurrentUploads = new Dictionary(); + /// A map from TransactionIDs to AvatarIDs + Dictionary> currentDownloads = new Dictionary>(); + + public PeriscopeTransferManager(Simian server, GridClient client) + { + this.server = server; + this.client = client; + + client.Assets.OnAssetReceived += new OpenMetaverse.AssetManager.AssetReceivedCallback(Assets_OnAssetReceived); + + server.UDP.RegisterPacketCallback(PacketType.AssetUploadRequest, new PacketCallback(AssetUploadRequestHandler)); + server.UDP.RegisterPacketCallback(PacketType.SendXferPacket, new PacketCallback(SendXferPacketHandler)); + server.UDP.RegisterPacketCallback(PacketType.AbortXfer, new PacketCallback(AbortXferHandler)); + server.UDP.RegisterPacketCallback(PacketType.TransferRequest, new PacketCallback(TransferRequestHandler)); + } + + public void Stop() + { + } + + #region Xfer System + + void AssetUploadRequestHandler(Packet packet, Agent agent) + { + AssetUploadRequestPacket request = (AssetUploadRequestPacket)packet; + UUID assetID = UUID.Combine(request.AssetBlock.TransactionID, agent.SecureSessionID); + + // Check if the asset is small enough to fit in a single packet + if (request.AssetBlock.AssetData.Length != 0) + { + // Create a new asset from the completed upload + Asset asset = CreateAsset((AssetType)request.AssetBlock.Type, assetID, request.AssetBlock.AssetData); + if (asset == null) + { + Logger.Log("Failed to create asset from uploaded data", Helpers.LogLevel.Warning); + return; + } + + Logger.DebugLog(String.Format("Storing uploaded asset {0} ({1})", assetID, asset.AssetType)); + + asset.Temporary = (request.AssetBlock.Tempfile | request.AssetBlock.StoreLocal); + + // Store the asset + server.Assets.StoreAsset(asset); + + // Send a success response + AssetUploadCompletePacket complete = new AssetUploadCompletePacket(); + complete.AssetBlock.Success = true; + complete.AssetBlock.Type = request.AssetBlock.Type; + complete.AssetBlock.UUID = assetID; + server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Inventory); + } + else + { + // Create a new (empty) asset for the upload + Asset asset = CreateAsset((AssetType)request.AssetBlock.Type, assetID, null); + if (asset == null) + { + Logger.Log("Failed to create asset from uploaded data", Helpers.LogLevel.Warning); + return; + } + + Logger.DebugLog(String.Format("Starting upload for {0} ({1})", assetID, asset.AssetType)); + + asset.Temporary = (request.AssetBlock.Tempfile | request.AssetBlock.StoreLocal); + + RequestXferPacket xfer = new RequestXferPacket(); + xfer.XferID.DeleteOnCompletion = request.AssetBlock.Tempfile; + xfer.XferID.FilePath = 0; + xfer.XferID.Filename = new byte[0]; + xfer.XferID.ID = request.AssetBlock.TransactionID.GetULong(); + xfer.XferID.UseBigPackets = false; + xfer.XferID.VFileID = asset.AssetID; + xfer.XferID.VFileType = request.AssetBlock.Type; + + // Add this asset to the current upload list + lock (CurrentUploads) + CurrentUploads[xfer.XferID.ID] = asset; + + server.UDP.SendPacket(agent.AgentID, xfer, PacketCategory.Inventory); + } + } + + void SendXferPacketHandler(Packet packet, Agent agent) + { + SendXferPacketPacket xfer = (SendXferPacketPacket)packet; + + Asset asset; + if (CurrentUploads.TryGetValue(xfer.XferID.ID, out asset)) + { + if (asset.AssetData == null) + { + if (xfer.XferID.Packet != 0) + { + Logger.Log(String.Format("Received Xfer packet {0} before the first packet!", + xfer.XferID.Packet), Helpers.LogLevel.Error); + return; + } + + uint size = Utils.BytesToUInt(xfer.DataPacket.Data); + asset.AssetData = new byte[size]; + + Buffer.BlockCopy(xfer.DataPacket.Data, 4, asset.AssetData, 0, xfer.DataPacket.Data.Length - 4); + + // Confirm the first upload packet + ConfirmXferPacketPacket confirm = new ConfirmXferPacketPacket(); + confirm.XferID.ID = xfer.XferID.ID; + confirm.XferID.Packet = xfer.XferID.Packet; + server.UDP.SendPacket(agent.AgentID, confirm, PacketCategory.Asset); + } + else + { + Buffer.BlockCopy(xfer.DataPacket.Data, 0, asset.AssetData, (int)xfer.XferID.Packet * 1000, + xfer.DataPacket.Data.Length); + + // Confirm this upload packet + ConfirmXferPacketPacket confirm = new ConfirmXferPacketPacket(); + confirm.XferID.ID = xfer.XferID.ID; + confirm.XferID.Packet = xfer.XferID.Packet; + server.UDP.SendPacket(agent.AgentID, confirm, PacketCategory.Asset); + + if ((xfer.XferID.Packet & (uint)0x80000000) != 0) + { + // Asset upload finished + Logger.DebugLog(String.Format("Completed Xfer upload of asset {0} ({1}", asset.AssetID, asset.AssetType)); + + lock (CurrentUploads) + CurrentUploads.Remove(xfer.XferID.ID); + + server.Assets.StoreAsset(asset); + + AssetUploadCompletePacket complete = new AssetUploadCompletePacket(); + complete.AssetBlock.Success = true; + complete.AssetBlock.Type = (sbyte)asset.AssetType; + complete.AssetBlock.UUID = asset.AssetID; + server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Asset); + } + } + } + else + { + Logger.DebugLog("Received a SendXferPacket for an unknown upload"); + } + } + + void AbortXferHandler(Packet packet, Agent agent) + { + AbortXferPacket abort = (AbortXferPacket)packet; + + lock (CurrentUploads) + { + if (CurrentUploads.ContainsKey(abort.XferID.ID)) + { + Logger.DebugLog(String.Format("Aborting Xfer {0}, result: {1}", abort.XferID.ID, + (TransferError)abort.XferID.Result)); + + CurrentUploads.Remove(abort.XferID.ID); + } + else + { + Logger.DebugLog(String.Format("Received an AbortXfer for an unknown xfer {0}", + abort.XferID.ID)); + } + } + } + + #endregion Xfer System + + #region Transfer System + + void TransferRequestHandler(Packet packet, Agent agent) + { + TransferRequestPacket request = (TransferRequestPacket)packet; + + ChannelType channel = (ChannelType)request.TransferInfo.ChannelType; + SourceType source = (SourceType)request.TransferInfo.SourceType; + + if (channel == ChannelType.Asset) + { + if (source == SourceType.Asset) + { + // Parse the request + UUID assetID = new UUID(request.TransferInfo.Params, 0); + AssetType type = (AssetType)(sbyte)Utils.BytesToInt(request.TransferInfo.Params, 16); + + // Check if we have this asset + Asset asset; + if (server.Assets.TryGetAsset(assetID, out asset)) + { + if (asset.AssetType == type) + { + TransferToClient(asset, agent, request.TransferInfo.TransferID); + } + else + { + Logger.Log(String.Format( + "Request for asset {0} with type {1} does not match actual asset type {2}", + assetID, type, asset.AssetType), Helpers.LogLevel.Warning); + } + } + else + { + // Use the bot to try and request this asset + lock (currentDownloads) + { + currentDownloads.Add(client.Assets.RequestAsset(assetID, type, false), + new KeyValuePair(agent.AgentID, request.TransferInfo.TransferID)); + } + } + } + else if (source == SourceType.SimEstate) + { + UUID agentID = new UUID(request.TransferInfo.Params, 0); + UUID sessionID = new UUID(request.TransferInfo.Params, 16); + EstateAssetType type = (EstateAssetType)Utils.BytesToInt(request.TransferInfo.Params, 32); + + Logger.Log("Please implement estate asset transfers", Helpers.LogLevel.Warning); + } + else if (source == SourceType.SimInventoryItem) + { + UUID agentID = new UUID(request.TransferInfo.Params, 0); + UUID sessionID = new UUID(request.TransferInfo.Params, 16); + UUID ownerID = new UUID(request.TransferInfo.Params, 32); + UUID taskID = new UUID(request.TransferInfo.Params, 48); + UUID itemID = new UUID(request.TransferInfo.Params, 64); + UUID assetID = new UUID(request.TransferInfo.Params, 80); + AssetType type = (AssetType)(sbyte)Utils.BytesToInt(request.TransferInfo.Params, 96); + + if (taskID != UUID.Zero) + { + // Task (prim) inventory request + Logger.Log("Please implement task inventory transfers", Helpers.LogLevel.Warning); + } + else + { + // Agent inventory request + Logger.Log("Please implement agent inventory transfer", Helpers.LogLevel.Warning); + } + } + else + { + Logger.Log(String.Format( + "Received a TransferRequest that we don't know how to handle. Channel: {0}, Source: {1}", + channel, source), Helpers.LogLevel.Warning); + } + } + else + { + Logger.Log(String.Format( + "Received a TransferRequest that we don't know how to handle. Channel: {0}, Source: {1}", + channel, source), Helpers.LogLevel.Warning); + } + } + + void TransferToClient(Asset asset, Agent agent, UUID transferID) + { + Logger.Log(String.Format("Transferring asset {0} ({1})", asset.AssetID, asset.AssetType), Helpers.LogLevel.Info); + + TransferInfoPacket response = new TransferInfoPacket(); + response.TransferInfo = new TransferInfoPacket.TransferInfoBlock(); + response.TransferInfo.TransferID = transferID; + + response.TransferInfo.Params = new byte[20]; + Buffer.BlockCopy(asset.AssetID.GetBytes(), 0, response.TransferInfo.Params, 0, 16); + Buffer.BlockCopy(Utils.IntToBytes((int)asset.AssetType), 0, response.TransferInfo.Params, 16, 4); + + response.TransferInfo.ChannelType = (int)ChannelType.Asset; + response.TransferInfo.Size = asset.AssetData.Length; + response.TransferInfo.Status = (int)StatusCode.OK; + response.TransferInfo.TargetType = (int)TargetType.Unknown; // Doesn't seem to be used by the client + + server.UDP.SendPacket(agent.AgentID, response, PacketCategory.Asset); + + // Transfer system does not wait for ACKs, just sends all of the + // packets for this transfer out + const int MAX_CHUNK_SIZE = Settings.MAX_PACKET_SIZE - 100; + int processedLength = 0; + int packetNum = 0; + while (processedLength < asset.AssetData.Length) + { + TransferPacketPacket transfer = new TransferPacketPacket(); + transfer.TransferData.ChannelType = (int)ChannelType.Asset; + transfer.TransferData.TransferID = transferID; + transfer.TransferData.Packet = packetNum++; + + int chunkSize = Math.Min(asset.AssetData.Length - processedLength, MAX_CHUNK_SIZE); + transfer.TransferData.Data = new byte[chunkSize]; + Buffer.BlockCopy(asset.AssetData, processedLength, transfer.TransferData.Data, 0, chunkSize); + processedLength += chunkSize; + + if (processedLength >= asset.AssetData.Length) + transfer.TransferData.Status = (int)StatusCode.Done; + else + transfer.TransferData.Status = (int)StatusCode.OK; + + server.UDP.SendPacket(agent.AgentID, transfer, PacketCategory.Asset); + } + } + + #endregion Transfer System + + void Assets_OnAssetReceived(AssetDownload transfer, Asset asset) + { + KeyValuePair kvp; + Agent agent; + if (currentDownloads.TryGetValue(transfer.ID, out kvp)) + { + currentDownloads.Remove(transfer.ID); + + if (server.Agents.TryGetValue(kvp.Key, out agent)) + { + if (transfer.Success) + { + server.Assets.StoreAsset(asset); + TransferToClient(asset, agent, kvp.Value); + } + else + { + Logger.Log("Request for missing asset " + transfer.AssetID.ToString(), Helpers.LogLevel.Warning); + + // Asset not found + TransferInfoPacket response = new TransferInfoPacket(); + response.TransferInfo = new TransferInfoPacket.TransferInfoBlock(); + response.TransferInfo.TransferID = kvp.Value; + + response.TransferInfo.Params = new byte[20]; + Buffer.BlockCopy(transfer.AssetID.GetBytes(), 0, response.TransferInfo.Params, 0, 16); + Buffer.BlockCopy(Utils.IntToBytes((int)transfer.AssetType), 0, response.TransferInfo.Params, 16, 4); + + response.TransferInfo.ChannelType = (int)ChannelType.Asset; + response.TransferInfo.Size = 0; + response.TransferInfo.Status = (int)StatusCode.UnknownSource; + response.TransferInfo.TargetType = (int)TargetType.Unknown; + + server.UDP.SendPacket(agent.AgentID, response, PacketCategory.Asset); + } + } + else + { + Logger.Log("Asset transfer finished for an untracked agent, ignoring", Helpers.LogLevel.Warning); + } + } + else + { + Logger.Log("Asset transfer finished for an untracked download, ignoring", Helpers.LogLevel.Warning); + } + } + + Asset CreateAsset(AssetType type, UUID assetID, byte[] data) + { + switch (type) + { + case AssetType.Bodypart: + return new AssetBodypart(assetID, data); + case AssetType.Clothing: + return new AssetClothing(assetID, data); + case AssetType.LSLBytecode: + return new AssetScriptBinary(assetID, data); + case AssetType.LSLText: + return new AssetScriptText(assetID, data); + case AssetType.Notecard: + return new AssetNotecard(assetID, data); + case AssetType.Texture: + return new AssetTexture(assetID, data); + case AssetType.Animation: + return new AssetAnimation(assetID, data); + case AssetType.CallingCard: + case AssetType.Folder: + case AssetType.Gesture: + case AssetType.ImageJPEG: + case AssetType.ImageTGA: + case AssetType.Landmark: + case AssetType.LostAndFoundFolder: + case AssetType.Object: + case AssetType.RootFolder: + case AssetType.Simstate: + case AssetType.SnapshotFolder: + case AssetType.Sound: + return new AssetSound(assetID, data); + case AssetType.SoundWAV: + case AssetType.TextureTGA: + case AssetType.TrashFolder: + case AssetType.Unknown: + default: + Logger.Log("Asset type " + type.ToString() + " not implemented!", Helpers.LogLevel.Warning); + return null; + } + } + } +} diff --git a/Programs/Simian/Extensions/TransferManager.cs b/Programs/Simian/Extensions/TransferManager.cs new file mode 100644 index 00000000..1c647cb3 --- /dev/null +++ b/Programs/Simian/Extensions/TransferManager.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections.Generic; +using ExtensionLoader; +using OpenMetaverse; +using OpenMetaverse.Packets; + +namespace Simian.Extensions +{ + public class TransferManager : IExtension + { + Simian server; + Dictionary CurrentUploads = new Dictionary(); + + public TransferManager() + { + } + + public void Start(Simian server) + { + this.server = server; + + server.UDP.RegisterPacketCallback(PacketType.AssetUploadRequest, new PacketCallback(AssetUploadRequestHandler)); + server.UDP.RegisterPacketCallback(PacketType.SendXferPacket, new PacketCallback(SendXferPacketHandler)); + server.UDP.RegisterPacketCallback(PacketType.AbortXfer, new PacketCallback(AbortXferHandler)); + server.UDP.RegisterPacketCallback(PacketType.TransferRequest, new PacketCallback(TransferRequestHandler)); + } + + public void Stop() + { + } + + + #region Xfer System + + void AssetUploadRequestHandler(Packet packet, Agent agent) + { + AssetUploadRequestPacket request = (AssetUploadRequestPacket)packet; + UUID assetID = UUID.Combine(request.AssetBlock.TransactionID, agent.SecureSessionID); + + // Check if the asset is small enough to fit in a single packet + if (request.AssetBlock.AssetData.Length != 0) + { + // Create a new asset from the completed upload + Asset asset = CreateAsset((AssetType)request.AssetBlock.Type, assetID, request.AssetBlock.AssetData); + if (asset == null) + { + Logger.Log("Failed to create asset from uploaded data", Helpers.LogLevel.Warning); + return; + } + + Logger.DebugLog(String.Format("Storing uploaded asset {0} ({1})", assetID, asset.AssetType)); + + asset.Temporary = (request.AssetBlock.Tempfile | request.AssetBlock.StoreLocal); + + // Store the asset + server.Assets.StoreAsset(asset); + + // Send a success response + AssetUploadCompletePacket complete = new AssetUploadCompletePacket(); + complete.AssetBlock.Success = true; + complete.AssetBlock.Type = request.AssetBlock.Type; + complete.AssetBlock.UUID = assetID; + server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Inventory); + } + else + { + // Create a new (empty) asset for the upload + Asset asset = CreateAsset((AssetType)request.AssetBlock.Type, assetID, null); + if (asset == null) + { + Logger.Log("Failed to create asset from uploaded data", Helpers.LogLevel.Warning); + return; + } + + Logger.DebugLog(String.Format("Starting upload for {0} ({1})", assetID, asset.AssetType)); + + asset.Temporary = (request.AssetBlock.Tempfile | request.AssetBlock.StoreLocal); + + RequestXferPacket xfer = new RequestXferPacket(); + xfer.XferID.DeleteOnCompletion = request.AssetBlock.Tempfile; + xfer.XferID.FilePath = 0; + xfer.XferID.Filename = new byte[0]; + xfer.XferID.ID = request.AssetBlock.TransactionID.GetULong(); + xfer.XferID.UseBigPackets = false; + xfer.XferID.VFileID = asset.AssetID; + xfer.XferID.VFileType = request.AssetBlock.Type; + + // Add this asset to the current upload list + lock (CurrentUploads) + CurrentUploads[xfer.XferID.ID] = asset; + + server.UDP.SendPacket(agent.AgentID, xfer, PacketCategory.Inventory); + } + } + + void SendXferPacketHandler(Packet packet, Agent agent) + { + SendXferPacketPacket xfer = (SendXferPacketPacket)packet; + + Asset asset; + if (CurrentUploads.TryGetValue(xfer.XferID.ID, out asset)) + { + if (asset.AssetData == null) + { + if (xfer.XferID.Packet != 0) + { + Logger.Log(String.Format("Received Xfer packet {0} before the first packet!", + xfer.XferID.Packet), Helpers.LogLevel.Error); + return; + } + + uint size = Utils.BytesToUInt(xfer.DataPacket.Data); + asset.AssetData = new byte[size]; + + Buffer.BlockCopy(xfer.DataPacket.Data, 4, asset.AssetData, 0, xfer.DataPacket.Data.Length - 4); + + // Confirm the first upload packet + ConfirmXferPacketPacket confirm = new ConfirmXferPacketPacket(); + confirm.XferID.ID = xfer.XferID.ID; + confirm.XferID.Packet = xfer.XferID.Packet; + server.UDP.SendPacket(agent.AgentID, confirm, PacketCategory.Asset); + } + else + { + Buffer.BlockCopy(xfer.DataPacket.Data, 0, asset.AssetData, (int)xfer.XferID.Packet * 1000, + xfer.DataPacket.Data.Length); + + // Confirm this upload packet + ConfirmXferPacketPacket confirm = new ConfirmXferPacketPacket(); + confirm.XferID.ID = xfer.XferID.ID; + confirm.XferID.Packet = xfer.XferID.Packet; + server.UDP.SendPacket(agent.AgentID, confirm, PacketCategory.Asset); + + if ((xfer.XferID.Packet & (uint)0x80000000) != 0) + { + // Asset upload finished + Logger.DebugLog(String.Format("Completed Xfer upload of asset {0} ({1}", asset.AssetID, asset.AssetType)); + + lock (CurrentUploads) + CurrentUploads.Remove(xfer.XferID.ID); + + server.Assets.StoreAsset(asset); + + AssetUploadCompletePacket complete = new AssetUploadCompletePacket(); + complete.AssetBlock.Success = true; + complete.AssetBlock.Type = (sbyte)asset.AssetType; + complete.AssetBlock.UUID = asset.AssetID; + server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Asset); + } + } + } + else + { + Logger.DebugLog("Received a SendXferPacket for an unknown upload"); + } + } + + void AbortXferHandler(Packet packet, Agent agent) + { + AbortXferPacket abort = (AbortXferPacket)packet; + + lock (CurrentUploads) + { + if (CurrentUploads.ContainsKey(abort.XferID.ID)) + { + Logger.DebugLog(String.Format("Aborting Xfer {0}, result: {1}", abort.XferID.ID, + (TransferError)abort.XferID.Result)); + + CurrentUploads.Remove(abort.XferID.ID); + } + else + { + Logger.DebugLog(String.Format("Received an AbortXfer for an unknown xfer {0}", + abort.XferID.ID)); + } + } + } + + #endregion Xfer System + + #region Transfer System + + void TransferRequestHandler(Packet packet, Agent agent) + { + TransferRequestPacket request = (TransferRequestPacket)packet; + + ChannelType channel = (ChannelType)request.TransferInfo.ChannelType; + SourceType source = (SourceType)request.TransferInfo.SourceType; + + if (channel == ChannelType.Asset) + { + // Construct the response packet + TransferInfoPacket response = new TransferInfoPacket(); + response.TransferInfo = new TransferInfoPacket.TransferInfoBlock(); + response.TransferInfo.TransferID = request.TransferInfo.TransferID; + + if (source == SourceType.Asset) + { + // Parse the request + UUID assetID = new UUID(request.TransferInfo.Params, 0); + AssetType type = (AssetType)(sbyte)Utils.BytesToInt(request.TransferInfo.Params, 16); + + // Set the response channel type + response.TransferInfo.ChannelType = (int)ChannelType.Asset; + + // Params + response.TransferInfo.Params = new byte[20]; + Buffer.BlockCopy(assetID.GetBytes(), 0, response.TransferInfo.Params, 0, 16); + Buffer.BlockCopy(Utils.IntToBytes((int)type), 0, response.TransferInfo.Params, 16, 4); + + // Check if we have this asset + Asset asset; + if (server.Assets.TryGetAsset(assetID, out asset)) + { + if (asset.AssetType == type) + { + Logger.DebugLog(String.Format("Transferring asset {0} ({1})", asset.AssetID, asset.AssetType)); + + // Asset found + response.TransferInfo.Size = asset.AssetData.Length; + response.TransferInfo.Status = (int)StatusCode.OK; + response.TransferInfo.TargetType = (int)TargetType.Unknown; // Doesn't seem to be used by the client + + server.UDP.SendPacket(agent.AgentID, response, PacketCategory.Asset); + + // Transfer system does not wait for ACKs, just sends all of the + // packets for this transfer out + const int MAX_CHUNK_SIZE = Settings.MAX_PACKET_SIZE - 100; + int processedLength = 0; + int packetNum = 0; + while (processedLength < asset.AssetData.Length) + { + TransferPacketPacket transfer = new TransferPacketPacket(); + transfer.TransferData.ChannelType = (int)ChannelType.Asset; + transfer.TransferData.TransferID = request.TransferInfo.TransferID; + transfer.TransferData.Packet = packetNum++; + + int chunkSize = Math.Min(asset.AssetData.Length - processedLength, MAX_CHUNK_SIZE); + transfer.TransferData.Data = new byte[chunkSize]; + Buffer.BlockCopy(asset.AssetData, processedLength, transfer.TransferData.Data, 0, chunkSize); + processedLength += chunkSize; + + if (processedLength >= asset.AssetData.Length) + transfer.TransferData.Status = (int)StatusCode.Done; + else + transfer.TransferData.Status = (int)StatusCode.OK; + + server.UDP.SendPacket(agent.AgentID, transfer, PacketCategory.Asset); + } + } + else + { + Logger.Log(String.Format( + "Request for asset {0} with type {1} does not match actual asset type {2}", + assetID, type, asset.AssetType), Helpers.LogLevel.Warning); + } + } + else + { + Logger.Log(String.Format("Request for missing asset {0} with type {1}", + assetID, type), Helpers.LogLevel.Warning); + + // Asset not found + response.TransferInfo.Size = 0; + response.TransferInfo.Status = (int)StatusCode.UnknownSource; + response.TransferInfo.TargetType = (int)TargetType.Unknown; + + server.UDP.SendPacket(agent.AgentID, response, PacketCategory.Asset); + } + } + else if (source == SourceType.SimEstate) + { + UUID agentID = new UUID(request.TransferInfo.Params, 0); + UUID sessionID = new UUID(request.TransferInfo.Params, 16); + EstateAssetType type = (EstateAssetType)Utils.BytesToInt(request.TransferInfo.Params, 32); + + Logger.Log("Please implement estate asset transfers", Helpers.LogLevel.Warning); + } + else if (source == SourceType.SimInventoryItem) + { + UUID agentID = new UUID(request.TransferInfo.Params, 0); + UUID sessionID = new UUID(request.TransferInfo.Params, 16); + UUID ownerID = new UUID(request.TransferInfo.Params, 32); + UUID taskID = new UUID(request.TransferInfo.Params, 48); + UUID itemID = new UUID(request.TransferInfo.Params, 64); + UUID assetID = new UUID(request.TransferInfo.Params, 80); + AssetType type = (AssetType)(sbyte)Utils.BytesToInt(request.TransferInfo.Params, 96); + + if (taskID != UUID.Zero) + { + // Task (prim) inventory request + Logger.Log("Please implement task inventory transfers", Helpers.LogLevel.Warning); + } + else + { + // Agent inventory request + Logger.Log("Please implement agent inventory transfer", Helpers.LogLevel.Warning); + } + } + else + { + Logger.Log(String.Format( + "Received a TransferRequest that we don't know how to handle. Channel: {0}, Source: {1}", + channel, source), Helpers.LogLevel.Warning); + } + } + else + { + Logger.Log(String.Format( + "Received a TransferRequest that we don't know how to handle. Channel: {0}, Source: {1}", + channel, source), Helpers.LogLevel.Warning); + } + } + + #endregion Transfer System + + Asset CreateAsset(AssetType type, UUID assetID, byte[] data) + { + switch (type) + { + case AssetType.Bodypart: + return new AssetBodypart(assetID, data); + case AssetType.Clothing: + return new AssetClothing(assetID, data); + case AssetType.LSLBytecode: + return new AssetScriptBinary(assetID, data); + case AssetType.LSLText: + return new AssetScriptText(assetID, data); + case AssetType.Notecard: + return new AssetNotecard(assetID, data); + case AssetType.Texture: + return new AssetTexture(assetID, data); + case AssetType.Animation: + return new AssetAnimation(assetID, data); + case AssetType.CallingCard: + case AssetType.Folder: + case AssetType.Gesture: + case AssetType.ImageJPEG: + case AssetType.ImageTGA: + case AssetType.Landmark: + case AssetType.LostAndFoundFolder: + case AssetType.Object: + case AssetType.RootFolder: + case AssetType.Simstate: + case AssetType.SnapshotFolder: + case AssetType.Sound: + return new AssetSound(assetID, data); + case AssetType.SoundWAV: + case AssetType.TextureTGA: + case AssetType.TrashFolder: + case AssetType.Unknown: + default: + Logger.Log("Asset type " + type.ToString() + " not implemented!", Helpers.LogLevel.Warning); + return null; + } + } + } +} diff --git a/bin/Simian.ini b/bin/Simian.ini index 16121cce..0c5f535b 100644 --- a/bin/Simian.ini +++ b/bin/Simian.ini @@ -48,6 +48,9 @@ Money ; Texture downloads ImageDelivery +; Other asset downloads +TransferManager + ; A simple physics engine for avatar movement. Supports walking, flying, and ; swimming as well as avatar-avatar collisions. Does not support avatar-prim ; or prim-prim collisions.