The client is a bot that can be run on the command-line and is controlled by the controller script from in-world. The client requires a configuration that is stored in Simscape.config
in the same directory as the executable.
A sample configuration file looks like this:
firstname:Simscape lastname:Sextant password:$1$a21bb8e950fda536faa10fc753a68a3d grid:https://login.agni.lindenlab.com/cgi-bin/login.cgi secret:PASSWORD master:52ec55a8-5792-4ec6-abc5-b92b6ae37c59
Where the following parameters are:
firstname
- is the first name of the user connecting to the server.lastname
- is the last name of the user connecting to the server.grid
- is the login URI for the grid, this can be left to the default for Second Life.secret
- this is a shared secret between Simscape and the controller script.master
- is the UUID of the agent that has superuser privileges for the bot.
For Second Life, everything aside from the grid
must be changed to your set-up.
/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2012 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// using System; using System.Text; using System.Text.RegularExpressions; using System.IO; using System.Net; using System.Threading; using System.Collections.Generic; using OpenMetaverse; using System.Linq; namespace Simscape { internal class Simscape { private static readonly GridClient Client = new GridClient(); private static string _firstName = string.Empty; private static string _lastName = string.Empty; private static string _password = string.Empty; private static List < UUID > _masters = new List < UUID > (); private static readonly Thread MainConnectionThread = new Thread(MainConnection); private static readonly Thread StatisticsThread = new Thread(SendStatistics); private static Dictionary < char, bool > StabilizeChecks = new Dictionary < char, bool > () { { 'l', false }, { 'e', false }, { 's', false }, { 'a', false } }; private static List < string > AuthAgents = new List < string > (); private static volatile bool _run = true; private static volatile bool _runStatistics = true; private static volatile string _url = string.Empty; public static void Main() { Settings.LOG_LEVEL = Helpers.LogLevel.None; Client.Settings.STORE_LAND_PATCHES = true; Client.Settings.ALWAYS_DECODE_OBJECTS = true; Client.Settings.ALWAYS_REQUEST_OBJECTS = true; Client.Settings.SEND_AGENT_UPDATES = true; Client.Settings.USE_ASSET_CACHE = false; Client.Settings.FETCH_MISSING_INVENTORY = true; Client.Network.LoginProgress += HandleLoginProgress; Client.Appearance.AppearanceSet += HandleAppearanceSet; Client.Network.SimConnected += HandleSimulatorConnected; Client.Network.Disconnected += HandleDisconnected; Client.Network.SimDisconnected += HandleSimulatorDisconnected; Client.Network.EventQueueRunning += HandleEventQueueRunning; Client.Friends.FriendshipOffered += HandleFriendshipOffered; Client.Self.IM += HandleSelfIM; try { foreach(string cfg in File.ReadLines("Simscape.config")) { string[] data = cfg.Split(new char[] { ':' }, 2); switch (data[0]) { case "firstname": _firstName = data[1]; break; case "lastname": _lastName = data[1]; break; case "password": _password = data[1]; break; case "grid": Client.Settings.LOGIN_SERVER = data[1]; break; // auth-secrets and masters are read dynamically on request. case "master": break; case "secret": break; default: Console.WriteLine("[Simscape] : Garbled line: " + cfg + ", the program will now terminate."); System.Environment.Exit(1); break; } } } catch (FileNotFoundException) { Console.WriteLine("[Simscape] : Could not read configuration file."); System.Environment.Exit(1); } new List < int > (new int[Console.WindowWidth]).ForEach(i => Console.Write("-")); MainConnectionThread.Start(); } private static void HandleDisconnected(object sender, DisconnectedEventArgs e) { Console.Write("\n[Simscape] : Disconnected..."); if (!StabilizeChecks.ContainsKey('l')) { StabilizeChecks.Add('l', false); return; } StabilizeChecks['l'] = false; } private static void HandleEventQueueRunning(object sender, EventQueueRunningEventArgs e) { Console.Write("\n[Simscape] : Event queue started..."); if (!StabilizeChecks.ContainsKey('e')) { StabilizeChecks.Add('e', true); return; } StabilizeChecks['e'] = true; } private static void HandleSimulatorConnected(object sender, SimConnectedEventArgs e) { Console.Write("\n[Simscape] : Simulator connected..."); if (!StabilizeChecks.ContainsKey('s')) { StabilizeChecks.Add('s', true); return; } StabilizeChecks['s'] = true; } private static void HandleSimulatorDisconnected(object sender, SimDisconnectedEventArgs e) { if (Client.Network.Simulators.Count >= 1) return; Console.Write("\n[Simscape] : Simulators disconnected..."); if (!StabilizeChecks.ContainsKey('s')) { StabilizeChecks.Add('s', false); return; } StabilizeChecks['s'] = false; } private static void HandleAppearanceSet(object sender, AppearanceSetEventArgs e) { if (e.Success) { Console.Write("\n[Simscape] : Appearance set..."); if (!StabilizeChecks.ContainsKey('a')) { StabilizeChecks.Add('a', true); return; } StabilizeChecks['a'] = true; return; } Console.Write("\n[Simscape] : Failed to set appearance..."); if (!StabilizeChecks.ContainsKey('a')) { StabilizeChecks.Add('a', false); return; } StabilizeChecks['a'] = false; } private static void HandleLoginProgress(object sender, LoginProgressEventArgs e) { switch (e.Status) { case LoginStatus.Success: Console.Write("\n[Simscape] : Login ok..."); StabilizeChecks['l'] = true; break; case LoginStatus.Failed: Console.Write("\n[Simscape] : Failed Login..."); if (!StabilizeChecks.ContainsKey('l')) { StabilizeChecks.Add('l', false); break; } StabilizeChecks['l'] = false; break; } } private static void HandleFriendshipOffered(object sender, FriendshipOfferedEventArgs e) { if (!_masters.Contains(e.AgentID)) return; Console.Write("\n[Simscape] : Accepting friendship with " + e.AgentName + "."); Client.Friends.AcceptFriendship(e.AgentID, e.SessionID); } private static void MainConnection() { StabilizeChecks = new Dictionary < char, bool > (); Client.Network.BeginLogin(new LoginParams(Client, _firstName, _lastName, _password, "[WaS] Corrade", "1.0")); Console.Write("\n[Corrade] : Stabilizing, please wait..."); do { Console.Write("."); Thread.Sleep(1000); } while (StabilizeChecks.Count != 4 && !StabilizeChecks.ContainsValue(false)); if (!StabilizeChecks.ContainsValue(false)) Console.Write("\n[Corrade] : Fully connected and accepting commands."); do { Thread.Sleep(1000); if (StabilizeChecks.ContainsValue(false)) { Console.Write("\n[Corrade] : Disconnected, shutting down..."); _run = false; } } while (_run); Console.Write("\n[Corrade] : All operations completed. Quitting."); new List < int > (new int[Console.WindowWidth]).ForEach(i => Console.Write("-")); Client.Network.Logout(); Client.Network.Shutdown(NetworkManager.DisconnectType.ClientInitiated); } private static void HandleSelfIM(object sender, InstantMessageEventArgs e) { if (e.IM.Dialog.Equals(InstantMessageDialog.MessageBox)) { Console.Write("\n[Simscape] : [Server Message] : " + e.IM.Message.Replace(System.Environment.NewLine, " ")); return; } if (e.IM.Dialog.Equals(InstantMessageDialog.RequestTeleport)) { try { foreach(string cfg in File.ReadLines("Simscape.config")) { string[] data = cfg.Split(new char[] { ':' }, 3); switch (data[0]) { case "master": var master = UUID.Zero; if (!UUID.TryParse(data[1], out master)) continue; if (_masters.Contains(master)) continue; _masters.Add(master); break; } } } catch (FileNotFoundException) { Console.Write("\n[Simscape] : Could not read configuration file."); System.Environment.Exit(1); } if (!_masters.Contains(e.IM.FromAgentID)) return; Console.Write("\n[Simscape] : Accepting teleport lure."); Client.Self.TeleportLureRespond(e.IM.FromAgentID, e.IM.IMSessionID, true); return; } // Ignore group chat. if (e.IM.GroupIM) return; var msg = e.IM.Message.Split(new char[] { ' ' }); if (!msg[0].Equals("auth")) goto NoAuth; try { foreach(string cfg in File.ReadLines("Simscape.config")) { string[] data = cfg.Split(new char[] { ':' }, 3); switch (data[0]) { case "secret": if (!data[1].Equals(msg[1])) break; if (!AuthAgents.Contains(e.IM.FromAgentName)) AuthAgents.Add(e.IM.FromAgentName); Console.Write("\n[Simscape] : Authenticated: " + e.IM.FromAgentName + "."); break; } } } catch (FileNotFoundException) { Console.Write("\n[Simscape] : Could not read configuration file."); System.Environment.Exit(1); } return; NoAuth: foreach(var agent in AuthAgents) { if (!agent.Equals(e.IM.FromAgentID)) continue; goto Command; } return; Command: if (msg[0].Equals("o")) goto TeleportCommand; if (msg[0].Equals("r")) goto RestartCommand; return; TeleportCommand: // Terminate the statistics thread. if (StatisticsThread.ThreadState.Equals(ThreadState.Running)) { _runStatistics = false; StatisticsThread.Join(); } // Get the URL. _url = msg[1]; // Pop the command off the array. var cmdArgs = new List < string > (msg); cmdArgs.RemoveAt(0); // Pop the URL off the array. cmdArgs.RemoveAt(0); // Get the rest of the command. var _newSimulatorName = String.Join(" ", cmdArgs.ToArray()); if (_newSimulatorName.Equals(Client.Network.CurrentSim.Name)) { Console.Write("\n[Simscape] : Already on simulator " + _newSimulatorName + "."); HTTPRequest(_url, "POST", "Teleport:FAILED"); return; } var TeleportEvent = new ManualResetEvent(false); Client.Self.TeleportProgress += delegate(object tps, TeleportEventArgs et) { if (et.Status.Equals(TeleportStatus.Finished)) { TeleportEvent.Set(); } }; Client.Self.Fly(true); Console.Write("\n[Simscape] : Teleporting to " + _newSimulatorName + "."); Client.Self.Teleport(_newSimulatorName, new Vector3(128, 128, 30000)); if (!TeleportEvent.WaitOne(10000, false)) { Console.Write("\n[Simscape] : Teleport failed to " + _newSimulatorName + "."); HTTPRequest(_url, "POST", "Teleport:FAILED"); TeleportEvent.Reset(); return; } TeleportEvent.Reset(); Console.Write("\n[Simscape] : Teleport to " + _newSimulatorName + " successful."); HTTPRequest(_url, "POST", "Teleport:SUCCESS"); _runStatistics = true; StatisticsThread.Start(); return; RestartCommand: return; } private static void SendStatistics() { do { HTTPRequest(_url, "PUT", String.Format("[{0}]|Active scripts: {1}|Agents: {2}|Child agents: {3}|Dilation: {4}|FPS: {5}|Objects: {6}|Physics FPS: {7}|Physics time: {8}|Scripted Objects: {9}|CPURatio: {10}\n", Client.Network.CurrentSim.Name, Client.Network.CurrentSim.Stats.ActiveScripts, Client.Network.CurrentSim.Stats.Agents, Client.Network.CurrentSim.Stats.ChildAgents, Client.Network.CurrentSim.Stats.Dilation, Client.Network.CurrentSim.Stats.FPS, Client.Network.CurrentSim.Stats.Objects, Client.Network.CurrentSim.Stats.PhysicsFPS, Client.Network.CurrentSim.Stats.PhysicsTime, Client.Network.CurrentSim.Stats.ScriptedObjects, Client.Network.CurrentSim.CPURatio)); Thread.Sleep(1000); } while (_runStatistics); } private static HttpWebResponse HTTPRequest(string url, string method, string data) { try { byte[] byteArray = Encoding.UTF8.GetBytes(data); WebRequest request = WebRequest.Create(url); request.Method = method; request.Timeout = 1000; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = byteArray.Length; Stream dataStream = request.GetRequestStream(); dataStream.Write(byteArray, 0, byteArray.Length); dataStream.Close(); HttpWebResponse response = (HttpWebResponse) request.GetResponse(); return response; } catch (WebException e) { return (HttpWebResponse) e.Response; } } } }