/*
* 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
}
}