/* * Copyright (c) 2006-2014, openmetaverse.org * 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 openmetaverse.org 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.IO.Compression; using System.Text; using System.Xml; using System.Threading; using OpenMetaverse; namespace OpenMetaverse.Assets { public static class OarFile { public delegate void AssetLoadedCallback(Asset asset, long bytesRead, long totalBytes); public delegate void TerrainLoadedCallback(float[,] terrain, long bytesRead, long totalBytes); public delegate void SceneObjectLoadedCallback(AssetPrim linkset, long bytesRead, long totalBytes); public delegate void SettingsLoadedCallback(string regionName, RegionSettings settings); #region Archive Loading public static void UnpackageArchive(string filename, AssetLoadedCallback assetCallback, TerrainLoadedCallback terrainCallback, SceneObjectLoadedCallback objectCallback, SettingsLoadedCallback settingsCallback) { int successfulAssetRestores = 0; int failedAssetRestores = 0; try { using (FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read)) { using (GZipStream loadStream = new GZipStream(fileStream, CompressionMode.Decompress)) { TarArchiveReader archive = new TarArchiveReader(loadStream); string filePath; byte[] data; TarArchiveReader.TarEntryType entryType; while ((data = archive.ReadEntry(out filePath, out entryType)) != null) { if (filePath.StartsWith(ArchiveConstants.OBJECTS_PATH)) { // Deserialize the XML bytes if (objectCallback != null) LoadObjects(data, objectCallback, fileStream.Position, fileStream.Length); } else if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) { if (assetCallback != null) { if (LoadAsset(filePath, data, assetCallback, fileStream.Position, fileStream.Length)) successfulAssetRestores++; else failedAssetRestores++; } } else if (filePath.StartsWith(ArchiveConstants.TERRAINS_PATH)) { if (terrainCallback != null) LoadTerrain(filePath, data, terrainCallback, fileStream.Position, fileStream.Length); } else if (filePath.StartsWith(ArchiveConstants.SETTINGS_PATH)) { if (settingsCallback != null) LoadRegionSettings(filePath, data, settingsCallback); } } archive.Close(); } } } catch (Exception e) { Logger.Log("[OarFile] Error loading OAR file: " + e.Message, Helpers.LogLevel.Error); return; } if (failedAssetRestores > 0) Logger.Log(String.Format("[OarFile]: Failed to load {0} assets", failedAssetRestores), Helpers.LogLevel.Warning); } private static bool LoadAsset(string assetPath, byte[] data, AssetLoadedCallback assetCallback, long bytesRead, long totalBytes) { // Right now we're nastily obtaining the UUID from the filename string filename = assetPath.Remove(0, ArchiveConstants.ASSETS_PATH.Length); int i = filename.LastIndexOf(ArchiveConstants.ASSET_EXTENSION_SEPARATOR); if (i == -1) { Logger.Log(String.Format( "[OarFile]: Could not find extension information in asset path {0} since it's missing the separator {1}. Skipping", assetPath, ArchiveConstants.ASSET_EXTENSION_SEPARATOR), Helpers.LogLevel.Warning); return false; } string extension = filename.Substring(i); UUID uuid; UUID.TryParse(filename.Remove(filename.Length - extension.Length), out uuid); if (ArchiveConstants.EXTENSION_TO_ASSET_TYPE.ContainsKey(extension)) { AssetType assetType = ArchiveConstants.EXTENSION_TO_ASSET_TYPE[extension]; Asset asset = null; switch (assetType) { case AssetType.Animation: asset = new AssetAnimation(uuid, data); break; case AssetType.Bodypart: asset = new AssetBodypart(uuid, data); break; case AssetType.Clothing: asset = new AssetClothing(uuid, data); break; case AssetType.Gesture: asset = new AssetGesture(uuid, data); break; case AssetType.Landmark: asset = new AssetLandmark(uuid, data); break; case AssetType.LSLBytecode: asset = new AssetScriptBinary(uuid, data); break; case AssetType.LSLText: asset = new AssetScriptText(uuid, data); break; case AssetType.Notecard: asset = new AssetNotecard(uuid, data); break; case AssetType.Object: asset = new AssetPrim(uuid, data); break; case AssetType.Sound: asset = new AssetSound(uuid, data); break; case AssetType.Texture: asset = new AssetTexture(uuid, data); break; default: Logger.Log("[OarFile] Unhandled asset type " + assetType, Helpers.LogLevel.Error); break; } if (asset != null) { assetCallback(asset, bytesRead, totalBytes); return true; } } Logger.Log("[OarFile] Failed to load asset", Helpers.LogLevel.Warning); return false; } private static bool LoadRegionSettings(string filePath, byte[] data, SettingsLoadedCallback settingsCallback) { RegionSettings settings = null; bool loaded = false; try { using (MemoryStream stream = new MemoryStream(data)) settings = RegionSettings.FromStream(stream); loaded = true; } catch (Exception ex) { Logger.Log("[OarFile] Failed to parse region settings file " + filePath + ": " + ex.Message, Helpers.LogLevel.Warning); } // Parse the region name out of the filename string regionName = Path.GetFileNameWithoutExtension(filePath); if (loaded) settingsCallback(regionName, settings); return loaded; } private static bool LoadTerrain(string filePath, byte[] data, TerrainLoadedCallback terrainCallback, long bytesRead, long totalBytes) { float[,] terrain = new float[256, 256]; bool loaded = false; switch (Path.GetExtension(filePath)) { case ".r32": case ".f32": // RAW32 if (data.Length == 256 * 256 * 4) { int pos = 0; for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { terrain[y, x] = Utils.Clamp(Utils.BytesToFloat(data, pos), 0.0f, 255.0f); pos += 4; } } loaded = true; } else { Logger.Log("[OarFile] RAW32 terrain file " + filePath + " has the wrong number of bytes: " + data.Length, Helpers.LogLevel.Warning); } break; case ".ter": // Terragen case ".raw": // LLRAW case ".jpg": case ".jpeg": // JPG case ".bmp": // BMP case ".png": // PNG case ".gif": // GIF case ".tif": case ".tiff": // TIFF default: Logger.Log("[OarFile] Unrecognized terrain format in " + filePath, Helpers.LogLevel.Warning); break; } if (loaded) terrainCallback(terrain, bytesRead, totalBytes); return loaded; } public static void LoadObjects(byte[] objectData, SceneObjectLoadedCallback objectCallback, long bytesRead, long totalBytes) { XmlDocument doc = new XmlDocument(); using (XmlTextReader reader = new XmlTextReader(new MemoryStream(objectData))) { reader.WhitespaceHandling = WhitespaceHandling.None; doc.Load(reader); } XmlNode rootNode = doc.FirstChild; if (rootNode.LocalName.Equals("scene")) { foreach (XmlNode node in rootNode.ChildNodes) { AssetPrim linkset = new AssetPrim(node.OuterXml); if (linkset != null) objectCallback(linkset, bytesRead, totalBytes); } } else { AssetPrim linkset = new AssetPrim(rootNode.OuterXml); if (linkset != null) objectCallback(linkset, bytesRead, totalBytes); } } #endregion Archive Loading #region Archive Saving public static void PackageArchive(string directoryName, string filename) { const string ARCHIVE_XML = "\n"; TarArchiveWriter archive = new TarArchiveWriter(new GZipStream(new FileStream(filename, FileMode.Create), CompressionMode.Compress)); // Create the archive.xml file archive.WriteFile("archive.xml", ARCHIVE_XML); // Add the assets string[] files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.ASSETS_PATH); foreach (string file in files) archive.WriteFile(ArchiveConstants.ASSETS_PATH + Path.GetFileName(file), File.ReadAllBytes(file)); // Add the objects files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.OBJECTS_PATH); foreach (string file in files) archive.WriteFile(ArchiveConstants.OBJECTS_PATH + Path.GetFileName(file), File.ReadAllBytes(file)); // Add the terrain(s) files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.TERRAINS_PATH); foreach (string file in files) archive.WriteFile(ArchiveConstants.TERRAINS_PATH + Path.GetFileName(file), File.ReadAllBytes(file)); // Add the parcels(s) files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.LANDDATA_PATH); foreach (string file in files) archive.WriteFile(ArchiveConstants.LANDDATA_PATH + Path.GetFileName(file), File.ReadAllBytes(file)); // Add the setting(s) files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.SETTINGS_PATH); foreach (string file in files) archive.WriteFile(ArchiveConstants.SETTINGS_PATH + Path.GetFileName(file), File.ReadAllBytes(file)); archive.Close(); } public static void SaveTerrain(Simulator sim, string terrainPath) { if (Directory.Exists(terrainPath)) Directory.Delete(terrainPath, true); Thread.Sleep(100); Directory.CreateDirectory(terrainPath); Thread.Sleep(100); FileInfo file = new FileInfo(Path.Combine(terrainPath, sim.Name + ".r32")); FileStream s = file.Open(FileMode.Create, FileAccess.Write); SaveTerrainStream(s, sim); s.Close(); } private static void SaveTerrainStream(Stream s, Simulator sim) { BinaryWriter bs = new BinaryWriter(s); int y; for (y = 0; y < 256; y++) { int x; for (x = 0; x < 256; x++) { float height; sim.TerrainHeightAtPoint(x, y, out height); bs.Write(height); } } bs.Close(); } public static void SaveParcels(Simulator sim, string parcelPath) { if (Directory.Exists(parcelPath)) Directory.Delete(parcelPath, true); Thread.Sleep(100); Directory.CreateDirectory(parcelPath); Thread.Sleep(100); sim.Parcels.ForEach((Parcel parcel) => { UUID globalID = UUID.Random(); SerializeParcel(parcel, globalID, Path.Combine(parcelPath, globalID + ".xml")); }); } private static void SerializeParcel(Parcel parcel, UUID globalID, string filename) { StringWriter sw = new StringWriter(); XmlTextWriter xtw = new XmlTextWriter(sw) { Formatting = Formatting.Indented }; xtw.WriteStartDocument(); xtw.WriteStartElement("LandData"); xtw.WriteElementString("Area", Convert.ToString(parcel.Area)); xtw.WriteElementString("AuctionID", Convert.ToString(parcel.AuctionID)); xtw.WriteElementString("AuthBuyerID", parcel.AuthBuyerID.ToString()); xtw.WriteElementString("Category", Convert.ToString((sbyte)parcel.Category)); TimeSpan t = parcel.ClaimDate.ToUniversalTime() - Utils.Epoch; xtw.WriteElementString("ClaimDate", Convert.ToString((int)t.TotalSeconds)); xtw.WriteElementString("ClaimPrice", Convert.ToString(parcel.ClaimPrice)); xtw.WriteElementString("GlobalID", globalID.ToString()); xtw.WriteElementString("GroupID", parcel.GroupID.ToString()); xtw.WriteElementString("IsGroupOwned", Convert.ToString(parcel.IsGroupOwned)); xtw.WriteElementString("Bitmap", Convert.ToBase64String(parcel.Bitmap)); xtw.WriteElementString("Description", parcel.Desc); xtw.WriteElementString("Flags", Convert.ToString((uint)parcel.Flags)); xtw.WriteElementString("LandingType", Convert.ToString((byte)parcel.Landing)); xtw.WriteElementString("Name", parcel.Name); xtw.WriteElementString("Status", Convert.ToString((sbyte)parcel.Status)); xtw.WriteElementString("LocalID", parcel.LocalID.ToString()); xtw.WriteElementString("MediaAutoScale", Convert.ToString(parcel.Media.MediaAutoScale ? 1 : 0)); xtw.WriteElementString("MediaID", parcel.Media.MediaID.ToString()); xtw.WriteElementString("MediaURL", parcel.Media.MediaURL); xtw.WriteElementString("MusicURL", parcel.MusicURL); xtw.WriteElementString("OwnerID", parcel.OwnerID.ToString()); xtw.WriteStartElement("ParcelAccessList"); foreach (ParcelManager.ParcelAccessEntry pal in parcel.AccessBlackList) { xtw.WriteStartElement("ParcelAccessEntry"); xtw.WriteElementString("AgentID", pal.AgentID.ToString()); xtw.WriteElementString("Time", pal.Time.ToString("s")); xtw.WriteElementString("AccessList", Convert.ToString((uint)pal.Flags)); xtw.WriteEndElement(); } foreach (ParcelManager.ParcelAccessEntry pal in parcel.AccessWhiteList) { xtw.WriteStartElement("ParcelAccessEntry"); xtw.WriteElementString("AgentID", pal.AgentID.ToString()); xtw.WriteElementString("Time", pal.Time.ToString("s")); xtw.WriteElementString("AccessList", Convert.ToString((uint)pal.Flags)); xtw.WriteEndElement(); } xtw.WriteEndElement(); xtw.WriteElementString("PassHours", Convert.ToString(parcel.PassHours)); xtw.WriteElementString("PassPrice", Convert.ToString(parcel.PassPrice)); xtw.WriteElementString("SalePrice", Convert.ToString(parcel.SalePrice)); xtw.WriteElementString("SnapshotID", parcel.SnapshotID.ToString()); xtw.WriteElementString("UserLocation", parcel.UserLocation.ToString()); xtw.WriteElementString("UserLookAt", parcel.UserLookAt.ToString()); xtw.WriteElementString("Dwell", "0"); xtw.WriteElementString("OtherCleanTime", Convert.ToString(parcel.OtherCleanTime)); xtw.WriteEndElement(); xtw.Close(); sw.Close(); File.WriteAllText(filename, sw.ToString()); } public static void SaveRegionSettings(Simulator sim, string settingsPath) { if (Directory.Exists(settingsPath)) Directory.Delete(settingsPath, true); Thread.Sleep(100); Directory.CreateDirectory(settingsPath); Thread.Sleep(100); RegionSettings settings = new RegionSettings(); //settings.AgentLimit; settings.AllowDamage = (sim.Flags & RegionFlags.AllowDamage) == RegionFlags.AllowDamage; //settings.AllowLandJoinDivide; settings.AllowLandResell = (sim.Flags & RegionFlags.BlockLandResell) != RegionFlags.BlockLandResell; settings.BlockFly = (sim.Flags & RegionFlags.NoFly) == RegionFlags.NoFly; settings.BlockLandShowInSearch = (sim.Flags & RegionFlags.BlockParcelSearch) == RegionFlags.BlockParcelSearch; settings.BlockTerraform = (sim.Flags & RegionFlags.BlockTerraform) == RegionFlags.BlockTerraform; settings.DisableCollisions = (sim.Flags & RegionFlags.SkipCollisions) == RegionFlags.SkipCollisions; settings.DisablePhysics = (sim.Flags & RegionFlags.SkipPhysics) == RegionFlags.SkipPhysics; settings.DisableScripts = (sim.Flags & RegionFlags.SkipScripts) == RegionFlags.SkipScripts; settings.FixedSun = (sim.Flags & RegionFlags.SunFixed) == RegionFlags.SunFixed; settings.MaturityRating = (int)(sim.Access & SimAccess.Mature & SimAccess.Adult & SimAccess.PG); //settings.ObjectBonus; settings.RestrictPushing = (sim.Flags & RegionFlags.RestrictPushObject) == RegionFlags.RestrictPushObject; settings.TerrainDetail0 = sim.TerrainDetail0; settings.TerrainDetail1 = sim.TerrainDetail1; settings.TerrainDetail2 = sim.TerrainDetail2; settings.TerrainDetail3 = sim.TerrainDetail3; settings.TerrainHeightRange00 = sim.TerrainHeightRange00; settings.TerrainHeightRange01 = sim.TerrainHeightRange01; settings.TerrainHeightRange10 = sim.TerrainHeightRange10; settings.TerrainHeightRange11 = sim.TerrainHeightRange11; settings.TerrainStartHeight00 = sim.TerrainStartHeight00; settings.TerrainStartHeight01 = sim.TerrainStartHeight01; settings.TerrainStartHeight10 = sim.TerrainStartHeight10; settings.TerrainStartHeight11 = sim.TerrainStartHeight11; //settings.UseEstateSun; settings.WaterHeight = sim.WaterHeight; settings.ToXML(Path.Combine(settingsPath, sim.Name + ".xml")); } public static void SavePrims(AssetManager manager, IList prims, string primsPath, string assetsPath) { Dictionary textureList = new Dictionary(); // Delete all of the old linkset files try { Directory.Delete(primsPath, true); } catch (Exception) { } Thread.Sleep(100); // Create a new folder for the linkset files try { Directory.CreateDirectory(primsPath); } catch (Exception ex) { Logger.Log("Failed saving prims: " + ex.Message, Helpers.LogLevel.Error); return; } Thread.Sleep(100); try { foreach (AssetPrim assetPrim in prims) { SavePrim(assetPrim, Path.Combine(primsPath, "Primitive_" + assetPrim.Parent.ID + ".xml")); CollectTextures(assetPrim.Parent, textureList); if (assetPrim.Children != null) { foreach (PrimObject child in assetPrim.Children) CollectTextures(child, textureList); } } SaveAssets(manager, AssetType.Texture, new List(textureList.Keys), assetsPath); } catch { } } static void CollectTextures(PrimObject prim, Dictionary textureList) { if (prim.Textures != null) { // Add all of the textures on this prim to the save list if (prim.Textures.DefaultTexture != null) textureList[prim.Textures.DefaultTexture.TextureID] = prim.Textures.DefaultTexture.TextureID; if (prim.Textures.FaceTextures != null) { for (int i = 0; i < prim.Textures.FaceTextures.Length; i++) { Primitive.TextureEntryFace face = prim.Textures.FaceTextures[i]; if (face != null) textureList[face.TextureID] = face.TextureID; } } if(prim.Sculpt != null && prim.Sculpt.Texture != UUID.Zero) textureList[prim.Sculpt.Texture] = prim.Sculpt.Texture; } } public static void ClearAssetFolder(string assetsPath) { // Delete the assets folder try { Directory.Delete(assetsPath, true); } catch (Exception) { } Thread.Sleep(100); // Create a new assets folder try { Directory.CreateDirectory(assetsPath); } catch (Exception ex) { Logger.Log("Failed saving assets: " + ex.Message, Helpers.LogLevel.Error); return; } Thread.Sleep(100); } public static void SaveAssets(AssetManager assetManager, AssetType assetType, IList assets, string assetsPath) { int count = 0; List remainingTextures = new List(assets); AutoResetEvent AllPropertiesReceived = new AutoResetEvent(false); for (int i = 0; i < assets.Count; i++) { UUID texture = assets[i]; if(assetType == AssetType.Texture) { assetManager.RequestImage(texture, (state, assetTexture) => { string extension = string.Empty; if (assetTexture == null) { Console.WriteLine("Missing asset " + texture); return; } if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(assetType)) extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[assetType]; File.WriteAllBytes(Path.Combine(assetsPath, texture.ToString() + extension), assetTexture.AssetData); remainingTextures.Remove(assetTexture.AssetID); if (remainingTextures.Count == 0) AllPropertiesReceived.Set(); ++count; }); } else { assetManager.RequestAsset(texture, assetType, false, (transfer, asset) => { string extension = string.Empty; if (asset == null) { Console.WriteLine("Missing asset " + texture); return; } if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(assetType)) extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[assetType]; File.WriteAllBytes(Path.Combine(assetsPath, texture.ToString() + extension), asset.AssetData); remainingTextures.Remove(asset.AssetID); if (remainingTextures.Count == 0) AllPropertiesReceived.Set(); ++count; }); } Thread.Sleep(200); if (i % 5 == 0) Thread.Sleep(250); } AllPropertiesReceived.WaitOne(5000 + 350 * assets.Count); Logger.Log("Copied " + count + " textures to the asset archive folder", Helpers.LogLevel.Info); } public static void SaveSimAssets(AssetManager assetManager, AssetType assetType, UUID assetID, UUID itemID, UUID primID, string assetsPath) { int count = 0; AutoResetEvent AllPropertiesReceived = new AutoResetEvent(false); assetManager.RequestAsset(assetID, itemID, primID, assetType, false, SourceType.SimInventoryItem, UUID.Random(), (transfer, asset) => { string extension = string.Empty; if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(assetType)) extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[assetType]; if (asset == null) { AllPropertiesReceived.Set(); return; } File.WriteAllBytes(Path.Combine(assetsPath, assetID.ToString() + extension), asset.AssetData); ++count; AllPropertiesReceived.Set(); }); AllPropertiesReceived.WaitOne(5000); Logger.Log("Copied " + count + " textures to the asset archive folder", Helpers.LogLevel.Info); } static void SavePrim(AssetPrim prim, string filename) { try { using (StreamWriter stream = new StreamWriter(filename)) { XmlTextWriter writer = new XmlTextWriter(stream); writer.Formatting = Formatting.Indented; writer.Indentation = 4; writer.IndentChar = ' '; SOGToXml2(writer, prim); writer.Flush(); } } catch (Exception ex) { Logger.Log("Failed saving linkset: " + ex.Message, Helpers.LogLevel.Error); } } public static void SOGToXml2(XmlTextWriter writer, AssetPrim prim) { writer.WriteStartElement(String.Empty, "SceneObjectGroup", String.Empty); SOPToXml(writer, prim.Parent, null); writer.WriteStartElement(String.Empty, "OtherParts", String.Empty); foreach (PrimObject child in prim.Children) SOPToXml(writer, child, prim.Parent); writer.WriteEndElement(); writer.WriteEndElement(); } static void SOPToXml(XmlTextWriter writer, PrimObject prim, PrimObject parent) { writer.WriteStartElement("SceneObjectPart"); writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); writer.WriteAttributeString("xmlns:xsd", "http://www.w3.org/2001/XMLSchema"); WriteUUID(writer, "CreatorID", prim.CreatorID); WriteUUID(writer, "FolderID", prim.FolderID); writer.WriteElementString("InventorySerial", (prim.Inventory != null) ? prim.Inventory.Serial.ToString() : "0"); // FIXME: Task inventory writer.WriteStartElement("TaskInventory"); if (prim.Inventory != null) { foreach (PrimObject.InventoryBlock.ItemBlock item in prim.Inventory.Items) { writer.WriteStartElement("", "TaskInventoryItem", ""); WriteUUID(writer, "AssetID", item.AssetID); writer.WriteElementString("BasePermissions", item.PermsBase.ToString()); writer.WriteElementString("CreationDate", (item.CreationDate.ToUniversalTime() - Utils.Epoch).TotalSeconds.ToString()); WriteUUID(writer, "CreatorID", item.CreatorID); writer.WriteElementString("Description", item.Description); writer.WriteElementString("EveryonePermissions", item.PermsEveryone.ToString()); writer.WriteElementString("Flags", item.Flags.ToString()); WriteUUID(writer, "GroupID", item.GroupID); writer.WriteElementString("GroupPermissions", item.PermsGroup.ToString()); writer.WriteElementString("InvType", ((int)item.InvType).ToString()); WriteUUID(writer, "ItemID", item.ID); WriteUUID(writer, "OldItemID", UUID.Zero); WriteUUID(writer, "LastOwnerID", item.LastOwnerID); writer.WriteElementString("Name", item.Name); writer.WriteElementString("NextPermissions", item.PermsNextOwner.ToString()); WriteUUID(writer, "OwnerID", item.OwnerID); writer.WriteElementString("CurrentPermissions", item.PermsOwner.ToString()); WriteUUID(writer, "ParentID", prim.ID); WriteUUID(writer, "ParentPartID", prim.ID); WriteUUID(writer, "PermsGranter", item.PermsGranterID); writer.WriteElementString("PermsMask", "0"); writer.WriteElementString("Type", ((int)item.Type).ToString()); writer.WriteElementString("OwnerChanged", "false"); writer.WriteEndElement(); } } writer.WriteEndElement(); PrimFlags flags = PrimFlags.None; if (prim.UsePhysics) flags |= PrimFlags.Physics; if (prim.Phantom) flags |= PrimFlags.Phantom; if (prim.DieAtEdge) flags |= PrimFlags.DieAtEdge; if (prim.ReturnAtEdge) flags |= PrimFlags.ReturnAtEdge; if (prim.Temporary) flags |= PrimFlags.Temporary; if (prim.Sandbox) flags |= PrimFlags.Sandbox; writer.WriteElementString("ObjectFlags", ((int)flags).ToString()); WriteUUID(writer, "UUID", prim.ID); writer.WriteElementString("LocalId", prim.LocalID.ToString()); writer.WriteElementString("Name", prim.Name); writer.WriteElementString("Material", ((int)prim.Material).ToString()); writer.WriteElementString("RegionHandle", prim.RegionHandle.ToString()); writer.WriteElementString("ScriptAccessPin", prim.RemoteScriptAccessPIN.ToString()); Vector3 groupPosition; if (parent == null) groupPosition = prim.Position; else groupPosition = parent.Position; WriteVector(writer, "GroupPosition", groupPosition); if (prim.ParentID == 0) WriteVector(writer, "OffsetPosition", Vector3.Zero); else WriteVector(writer, "OffsetPosition", prim.Position); WriteQuaternion(writer, "RotationOffset", prim.Rotation); WriteVector(writer, "Velocity", prim.Velocity); WriteVector(writer, "RotationalVelocity", Vector3.Zero); WriteVector(writer, "AngularVelocity", prim.AngularVelocity); WriteVector(writer, "Acceleration", prim.Acceleration); writer.WriteElementString("Description", prim.Description); writer.WriteStartElement("Color"); writer.WriteElementString("R", prim.TextColor.R.ToString(Utils.EnUsCulture)); writer.WriteElementString("G", prim.TextColor.G.ToString(Utils.EnUsCulture)); writer.WriteElementString("B", prim.TextColor.B.ToString(Utils.EnUsCulture)); writer.WriteElementString("A", prim.TextColor.G.ToString(Utils.EnUsCulture)); writer.WriteEndElement(); writer.WriteElementString("Text", prim.Text); writer.WriteElementString("SitName", prim.SitName); writer.WriteElementString("TouchName", prim.TouchName); writer.WriteElementString("LinkNum", prim.LinkNumber.ToString()); writer.WriteElementString("ClickAction", prim.ClickAction.ToString()); writer.WriteStartElement("Shape"); writer.WriteElementString("PathBegin", Primitive.PackBeginCut(prim.Shape.PathBegin).ToString()); writer.WriteElementString("PathCurve", prim.Shape.PathCurve.ToString()); writer.WriteElementString("PathEnd", Primitive.PackEndCut(prim.Shape.PathEnd).ToString()); writer.WriteElementString("PathRadiusOffset", Primitive.PackPathTwist(prim.Shape.PathRadiusOffset).ToString()); writer.WriteElementString("PathRevolutions", Primitive.PackPathRevolutions(prim.Shape.PathRevolutions).ToString()); writer.WriteElementString("PathScaleX", Primitive.PackPathScale(prim.Shape.PathScaleX).ToString()); writer.WriteElementString("PathScaleY", Primitive.PackPathScale(prim.Shape.PathScaleY).ToString()); writer.WriteElementString("PathShearX", ((byte)Primitive.PackPathShear(prim.Shape.PathShearX)).ToString()); writer.WriteElementString("PathShearY", ((byte)Primitive.PackPathShear(prim.Shape.PathShearY)).ToString()); writer.WriteElementString("PathSkew", Primitive.PackPathTwist(prim.Shape.PathSkew).ToString()); writer.WriteElementString("PathTaperX", Primitive.PackPathTaper(prim.Shape.PathTaperX).ToString()); writer.WriteElementString("PathTaperY", Primitive.PackPathTaper(prim.Shape.PathTaperY).ToString()); writer.WriteElementString("PathTwist", Primitive.PackPathTwist(prim.Shape.PathTwist).ToString()); writer.WriteElementString("PathTwistBegin", Primitive.PackPathTwist(prim.Shape.PathTwistBegin).ToString()); writer.WriteElementString("PCode", prim.PCode.ToString()); writer.WriteElementString("ProfileBegin", Primitive.PackBeginCut(prim.Shape.ProfileBegin).ToString()); writer.WriteElementString("ProfileEnd", Primitive.PackEndCut(prim.Shape.ProfileEnd).ToString()); writer.WriteElementString("ProfileHollow", Primitive.PackProfileHollow(prim.Shape.ProfileHollow).ToString()); WriteVector(writer, "Scale", prim.Scale); writer.WriteElementString("State", prim.State.ToString()); AssetPrim.ProfileShape shape = (AssetPrim.ProfileShape)(prim.Shape.ProfileCurve & 0x0F); HoleType hole = (HoleType)(prim.Shape.ProfileCurve & 0xF0); writer.WriteElementString("ProfileShape", shape.ToString()); writer.WriteElementString("HollowShape", hole.ToString()); writer.WriteElementString("ProfileCurve", prim.Shape.ProfileCurve.ToString()); writer.WriteStartElement("TextureEntry"); byte[] te; if (prim.Textures != null) te = prim.Textures.GetBytes(); else te = Utils.EmptyBytes; writer.WriteBase64(te, 0, te.Length); writer.WriteEndElement(); // FIXME: ExtraParams writer.WriteStartElement("ExtraParams"); writer.WriteEndElement(); writer.WriteEndElement(); WriteVector(writer, "Scale", prim.Scale); writer.WriteElementString("UpdateFlag", "0"); WriteVector(writer, "SitTargetOrientation", Vector3.UnitZ); // TODO: Is this really a vector and not a quaternion? WriteVector(writer, "SitTargetPosition", prim.SitOffset); WriteVector(writer, "SitTargetPositionLL", prim.SitOffset); WriteQuaternion(writer, "SitTargetOrientationLL", prim.SitRotation); writer.WriteElementString("ParentID", prim.ParentID.ToString()); writer.WriteElementString("CreationDate", ((int)Utils.DateTimeToUnixTime(prim.CreationDate)).ToString()); writer.WriteElementString("Category", "0"); writer.WriteElementString("SalePrice", prim.SalePrice.ToString()); writer.WriteElementString("ObjectSaleType", ((int)prim.SaleType).ToString()); writer.WriteElementString("OwnershipCost", "0"); WriteUUID(writer, "GroupID", prim.GroupID); WriteUUID(writer, "OwnerID", prim.OwnerID); WriteUUID(writer, "LastOwnerID", prim.LastOwnerID); writer.WriteElementString("BaseMask", ((uint)PermissionMask.All).ToString()); writer.WriteElementString("OwnerMask", ((uint)PermissionMask.All).ToString()); writer.WriteElementString("GroupMask", ((uint)PermissionMask.All).ToString()); writer.WriteElementString("EveryoneMask", ((uint)PermissionMask.All).ToString()); writer.WriteElementString("NextOwnerMask", ((uint)PermissionMask.All).ToString()); writer.WriteElementString("Flags", "None"); WriteUUID(writer, "SitTargetAvatar", UUID.Zero); writer.WriteEndElement(); } static void WriteUUID(XmlTextWriter writer, string name, UUID id) { writer.WriteStartElement(name); writer.WriteElementString("UUID", id.ToString()); writer.WriteEndElement(); } static void WriteVector(XmlTextWriter writer, string name, Vector3 vec) { writer.WriteStartElement(name); writer.WriteElementString("X", vec.X.ToString(Utils.EnUsCulture)); writer.WriteElementString("Y", vec.Y.ToString(Utils.EnUsCulture)); writer.WriteElementString("Z", vec.Z.ToString(Utils.EnUsCulture)); writer.WriteEndElement(); } static void WriteQuaternion(XmlTextWriter writer, string name, Quaternion quat) { writer.WriteStartElement(name); writer.WriteElementString("X", quat.X.ToString(Utils.EnUsCulture)); writer.WriteElementString("Y", quat.Y.ToString(Utils.EnUsCulture)); writer.WriteElementString("Z", quat.Z.ToString(Utils.EnUsCulture)); writer.WriteElementString("W", quat.W.ToString(Utils.EnUsCulture)); writer.WriteEndElement(); } #endregion Archive Saving } }