using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Xml; using OpenMetaverse; namespace SimExport { public class SimExport { GridClient client; TexturePipeline texturePipeline; volatile bool running; int totalPrims = -1; object totalPrimsLock = new object(); DoubleDictionary prims = new DoubleDictionary(); Dictionary selectedPrims = new Dictionary(); Dictionary texturesFinished = new Dictionary(); BlockingQueue primsAwaitingSelect = new BlockingQueue(); string filename; string directoryname; public SimExport(string firstName, string lastName, string password, string loginServer, string regionName, string filename) { this.filename = filename; directoryname = Path.GetFileNameWithoutExtension(filename); try { if (!Directory.Exists(directoryname)) Directory.CreateDirectory(filename); if (!Directory.Exists(directoryname + "/assets")) Directory.CreateDirectory(directoryname + "/assets"); if (!Directory.Exists(directoryname + "/objects")) Directory.CreateDirectory(directoryname + "/objects"); if (!Directory.Exists(directoryname + "/terrains")) Directory.CreateDirectory(directoryname + "/terrains"); CheckTextures(); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error); return; } running = true; client = new GridClient(); texturePipeline = new TexturePipeline(client, 10); texturePipeline.OnDownloadFinished += new TexturePipeline.DownloadFinishedCallback(texturePipeline_OnDownloadFinished); //Settings.LOG_LEVEL = Helpers.LogLevel.Info; client.Settings.MULTIPLE_SIMS = false; client.Settings.PARCEL_TRACKING = true; client.Settings.ALWAYS_REQUEST_PARCEL_ACL = true; client.Settings.ALWAYS_REQUEST_PARCEL_DWELL = false; client.Settings.ALWAYS_REQUEST_OBJECTS = true; client.Settings.STORE_LAND_PATCHES = true; client.Settings.SEND_AGENT_UPDATES = true; client.Settings.DISABLE_AGENT_UPDATE_DUPLICATE_CHECK = true; client.Network.OnCurrentSimChanged += Network_OnCurrentSimChanged; client.Objects.OnNewPrim += Objects_OnNewPrim; client.Objects.OnObjectKilled += Objects_OnObjectKilled; client.Objects.OnObjectProperties += Objects_OnObjectProperties; client.Objects.OnObjectUpdated += Objects_OnObjectUpdated; client.Parcels.OnSimParcelsDownloaded += new ParcelManager.SimParcelsDownloaded(Parcels_OnSimParcelsDownloaded); LoginParams loginParams = client.Network.DefaultLoginParams(firstName, lastName, password, "SimExport", "0.0.1"); loginParams.URI = loginServer; loginParams.Start = NetworkManager.StartLocation(regionName, 128, 128, 40); if (client.Network.Login(loginParams)) { Run(); } else { Logger.Log(String.Format("Login failed ({0}: {1}", client.Network.LoginErrorKey, client.Network.LoginMessage), Helpers.LogLevel.Error); } } void CheckTextures() { lock (texturesFinished) { string[] files = Directory.GetFiles(directoryname + "/assets", "*.jp2"); foreach (string file in files) { // Parse the UUID out of the filename UUID id; if (UUID.TryParse(Path.GetFileNameWithoutExtension(file).Substring(0, 36), out id)) texturesFinished[id] = id; } } Logger.Log(String.Format("Found {0} previously downloaded texture assets", texturesFinished.Count), Helpers.LogLevel.Info); } void texturePipeline_OnDownloadFinished(UUID id, bool success) { if (success) { // Save this texture to the hard drive ImageDownload image = texturePipeline.GetTextureToRender(id); try { File.WriteAllBytes(directoryname + "/assets/" + id.ToString() + "_texture.jp2", image.AssetData); lock (texturesFinished) texturesFinished[id] = id; } catch (Exception ex) { Logger.Log("Failed to save texture: " + ex.Message, Helpers.LogLevel.Error); } } else { Logger.Log("Texture failed to download: " + id.ToString(), Helpers.LogLevel.Warning); } } void Run() { // Start the thread that monitors the queue of prims that need ObjectSelect packets sent Thread thread = new Thread(new ThreadStart(MonitorPrimsAwaitingSelect)); thread.Start(); while (running) { string command = Console.ReadLine(); switch (command) { case "queue": Logger.Log(String.Format("Client Outbox contains {0} packets, ObjectSelect queue contains {1} prims", client.Network.OutboxCount, primsAwaitingSelect.Count), Helpers.LogLevel.Info); break; case "prims": Logger.Log(String.Format("Prims captured: {0}, Total: {1}", prims.Count, totalPrims), Helpers.LogLevel.Info); break; case "parcels": if (!client.Network.CurrentSim.IsParcelMapFull()) { Logger.Log("Downloading sim parcel information and prim totals", Helpers.LogLevel.Info); client.Parcels.RequestAllSimParcels(client.Network.CurrentSim, false, 10); } else { Logger.Log("Sim parcel information has been retrieved", Helpers.LogLevel.Info); } break; case "camera": Thread cameraThread = new Thread(new ThreadStart(MoveCamera)); cameraThread.Start(); Logger.Log("Started random camera movement thread", Helpers.LogLevel.Info); break; case "movement": Vector3 destination = RandomPosition(); Logger.Log("Teleporting to " + destination.ToString(), Helpers.LogLevel.Info); client.Self.Teleport(client.Network.CurrentSim.Handle, destination, RandomPosition()); break; case "textures": Logger.Log(String.Format("Current texture requests: {0}, queued texture requests: {1}, completed textures: {2}", texturePipeline.CurrentCount, texturePipeline.QueuedCount, texturesFinished.Count), Helpers.LogLevel.Info); break; case "terrain": TerrainPatch[] patches; if (client.Terrain.SimPatches.TryGetValue(client.Network.CurrentSim.Handle, out patches)) { int count = 0; for (int i = 0; i < patches.Length; i++) { if (patches[i] != null) ++count; } Logger.Log(count + " terrain patches have been received for the current simulator", Helpers.LogLevel.Info); } else { Logger.Log("No terrain information received for the current simulator", Helpers.LogLevel.Info); } break; case "saveterrain": if (client.Terrain.SimPatches.TryGetValue(client.Network.CurrentSim.Handle, out patches)) { try { using (FileStream stream = new FileStream(directoryname + "/terrains/heightmap.r32", FileMode.Create, FileAccess.Write)) { for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { int xBlock = x / 16; int yBlock = y / 16; int xOff = x - (xBlock * 16); int yOff = y - (yBlock * 16); TerrainPatch patch = patches[yBlock * 16 + xBlock]; float t = 0f; if (patch != null) t = patch.Data[yOff * 16 + xOff]; else Logger.Log(String.Format("Skipping missing patch at {0},{1}", xBlock, yBlock), Helpers.LogLevel.Warning); stream.Write(BitConverter.GetBytes(t), 0, 4); } } } } catch (Exception ex) { Logger.Log("Failed saving terrain: " + ex.Message, Helpers.LogLevel.Error); } } else { Logger.Log("No terrain information received for the current simulator", Helpers.LogLevel.Info); } break; case "save": Logger.Log(String.Format("Preparing to serialize {0} objects", prims.Count), Helpers.LogLevel.Info); OarFile.SavePrims(prims, directoryname + "/objects"); Logger.Log("Saving " + directoryname, Helpers.LogLevel.Info); OarFile.PackageArchive(directoryname, filename); Logger.Log("Done", Helpers.LogLevel.Info); break; case "quit": End(); break; } } } Random random = new Random(); Vector3 RandomPosition() { float x = (float)(random.NextDouble() * 256d); float y = (float)(random.NextDouble() * 128d); float z = (float)(random.NextDouble() * 256d); return new Vector3(x, y, z); } void MoveCamera() { while (running) { if (client.Network.Connected) { // TWEAK: Randomize far distance to force an interest list recomputation float far = (float)(random.NextDouble() * 252d + 4d); // Random small movements AgentManager.ControlFlags flags = AgentManager.ControlFlags.NONE; if (far < 96f) flags |= AgentManager.ControlFlags.AGENT_CONTROL_TURN_LEFT; else if (far < 196f) flags |= AgentManager.ControlFlags.AGENT_CONTROL_TURN_RIGHT; else if (far < 212f) flags |= AgentManager.ControlFlags.AGENT_CONTROL_UP_POS; else flags |= AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG; // Randomly change the camera position Vector3 pos = RandomPosition(); client.Self.Movement.SendManualUpdate( flags, pos, Vector3.UnitZ, Vector3.UnitX, Vector3.UnitY, Quaternion.Identity, Quaternion.Identity, far, AgentFlags.None, AgentState.None, false); } Thread.Sleep(500); } } void End() { texturePipeline.Shutdown(); if (client.Network.Connected) { if (Program.Verbosity > 0) Logger.Log("Logging out", Helpers.LogLevel.Info); client.Network.Logout(); } running = false; } void MonitorPrimsAwaitingSelect() { while (running) { try { Primitive prim = primsAwaitingSelect.Dequeue(250); if (!prims.ContainsKey(prim.LocalID) && prim != null) { client.Objects.SelectObject(client.Network.CurrentSim, prim.LocalID); Thread.Sleep(20); // Hacky rate limiting } } catch (InvalidOperationException) { } } } void Network_OnCurrentSimChanged(Simulator PreviousSimulator) { if (Program.Verbosity > 0) Logger.Log("Moved into simulator " + client.Network.CurrentSim.ToString(), Helpers.LogLevel.Info); } void Parcels_OnSimParcelsDownloaded(Simulator simulator, InternalDictionary simParcels, int[,] parcelMap) { lock (totalPrimsLock) { totalPrims = 0; simParcels.ForEach( delegate(Parcel parcel) { totalPrims += parcel.TotalPrims; }); if (Program.Verbosity > 0) Logger.Log(String.Format("Counted {0} total prims in this simulator", totalPrims), Helpers.LogLevel.Info); } } void Objects_OnNewPrim(Simulator simulator, Primitive prim, ulong regionHandle, ushort timeDilation) { prims.Add(prim.LocalID, prim.ID, prim); primsAwaitingSelect.Enqueue(prim); UpdateTextureQueue(prim.Textures); } void UpdateTextureQueue(Primitive.TextureEntry te) { if (te != null) { 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, ImageType.Normal); } } } void Objects_OnObjectUpdated(Simulator simulator, ObjectUpdate update, ulong regionHandle, ushort timeDilation) { if (!update.Avatar) { Primitive prim; if (prims.TryGetValue(update.LocalID, out prim)) { lock (prim) { if (Program.Verbosity > 1) Logger.Log("Updating state for " + prim.ID.ToString(), Helpers.LogLevel.Info); prim.Acceleration = update.Acceleration; prim.AngularVelocity = update.AngularVelocity; prim.CollisionPlane = update.CollisionPlane; prim.Position = update.Position; prim.Rotation = update.Rotation; prim.PrimData.State = update.State; prim.Textures = update.Textures; prim.Velocity = update.Velocity; } UpdateTextureQueue(prim.Textures); } } } void Objects_OnObjectProperties(Simulator simulator, Primitive.ObjectProperties props) { Primitive prim; if (prims.TryGetValue(props.ObjectID, out prim)) { if (Program.Verbosity > 2) Logger.Log("Received properties for " + props.ObjectID.ToString(), Helpers.LogLevel.Info); lock (prim) prim.Properties = props; } else { Logger.Log("Received object properties for untracked object " + props.ObjectID.ToString(), Helpers.LogLevel.Warning); } } void Objects_OnObjectKilled(Simulator simulator, uint objectID) { ; } } public class Program { public static int Verbosity = 0; static void Main(string[] args) { string loginServer = Settings.AGNI_LOGIN_SERVER; string filename = "simexport.tgz"; string regionName = null, firstName = null, lastName = null, password = null; bool showhelp = false; NDesk.Options.OptionSet argParser = new NDesk.Options.OptionSet() .Add("s|login-server=", "URL of the login server (default is '" + loginServer + "')", delegate(string v) { loginServer = v; }) .Add("r|region-name=", "name of the region to export", delegate(string v) { regionName = v; }) .Add("f|firstname=", "first name of the bot to log in", delegate(string v) { firstName = v; }) .Add("l|lastname=", "last name of the bot to log in", delegate(string v) { lastName = v; }) .Add("p|password=", "password of the bot to log in", delegate(string v) { password = v; }) .Add("o|output=", "filename of the OAR to write (default is 'simexport.tgz')", delegate(string v) { filename = v; }) .Add("h|?|help", delegate(string v) { showhelp = (v != null); }) .Add("v|verbose", delegate(string v) { if (v != null) ++Verbosity; }); argParser.Parse(args); if (!showhelp && !String.IsNullOrEmpty(regionName) && !String.IsNullOrEmpty(firstName) && !String.IsNullOrEmpty(lastName) && !String.IsNullOrEmpty(password)) { SimExport exporter = new SimExport(firstName, lastName, password, loginServer, regionName, filename); } else { Console.WriteLine("Usage: SimExport.exe [OPTION]..."); Console.WriteLine("An interactive client for exporting assets"); Console.WriteLine("Options:"); argParser.WriteOptionDescriptions(Console.Out); } } } }