git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@171 52acb1d6-8a22-11de-b505-999d5b087335
973 lines
28 KiB
C#
973 lines
28 KiB
C#
/*
|
|
* Copyright (c) 2006, Second Life Reverse Engineering Team
|
|
* 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 Second Life Reverse Engineering Team 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.Text;
|
|
using System.Timers;
|
|
using System.Collections;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Threading;
|
|
using System.Security.Cryptography;
|
|
using Nwc.XmlRpc;
|
|
using Nii.JSON;
|
|
|
|
namespace libsecondlife
|
|
{
|
|
public delegate void PacketCallback(Packet packet, Simulator simulator);
|
|
|
|
public class NotConnectedException : ApplicationException { }
|
|
|
|
internal class AcceptAllCertificatePolicy : ICertificatePolicy
|
|
{
|
|
public AcceptAllCertificatePolicy()
|
|
{
|
|
}
|
|
|
|
public bool CheckValidationResult(ServicePoint sPoint,
|
|
System.Security.Cryptography.X509Certificates.X509Certificate cert,
|
|
WebRequest wRequest,int certProb)
|
|
{
|
|
// Always accept
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public class Simulator
|
|
{
|
|
private ProtocolManager Protocol;
|
|
private NetworkManager Network;
|
|
private Hashtable Callbacks;
|
|
private ushort Sequence;
|
|
private byte[] RecvBuffer;
|
|
private Mutex RecvBufferMutex = new Mutex(false, "RecvBufferMutex");
|
|
private Socket Connection;
|
|
private AsyncCallback ReceivedData;
|
|
private Hashtable NeedAck;
|
|
private Mutex NeedAckMutex;
|
|
private SortedList Inbox;
|
|
private Mutex InboxMutex;
|
|
private bool connected;
|
|
|
|
private uint circuitCode;
|
|
private IPEndPoint ipEndPoint;
|
|
private EndPoint endPoint;
|
|
|
|
public uint CircuitCode
|
|
{
|
|
get { return circuitCode; }
|
|
}
|
|
public IPEndPoint IPEndPoint
|
|
{
|
|
get { return ipEndPoint; }
|
|
}
|
|
public bool Connected
|
|
{
|
|
get { return connected; }
|
|
}
|
|
public Region Region;
|
|
|
|
public Simulator(SecondLife client, Hashtable callbacks, uint circuit,
|
|
IPAddress ip, int port)
|
|
{
|
|
Initialize(client, callbacks, circuit, ip, port);
|
|
}
|
|
|
|
private void Initialize(SecondLife client, Hashtable callbacks, uint circuit,
|
|
IPAddress ip, int port)
|
|
{
|
|
Protocol = client.Protocol;
|
|
Network = client.Network;
|
|
Callbacks = callbacks;
|
|
Region = new Region(client);
|
|
circuitCode = circuit;
|
|
Sequence = 0;
|
|
RecvBuffer = new byte[2048];
|
|
Connection = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
|
connected = false;
|
|
|
|
// Initialize the hashtable for reliable packets waiting on ACKs from the server
|
|
NeedAck = new Hashtable();
|
|
|
|
// Initialize the list of sequence numbers we've received so far
|
|
Inbox = new SortedList();
|
|
|
|
NeedAckMutex = new Mutex(false, "NeedAckMutex");
|
|
InboxMutex = new Mutex(false, "InboxMutex");
|
|
|
|
Helpers.Log("Connecting to " + ip.ToString() + ":" + port, Helpers.LogLevel.Info);
|
|
|
|
try
|
|
{
|
|
// Setup the callback
|
|
ReceivedData = new AsyncCallback(OnReceivedData);
|
|
|
|
// Create an endpoint that we will be communicating with (need it in two
|
|
// types due to .NET weirdness)
|
|
ipEndPoint = new IPEndPoint(ip, port);
|
|
endPoint = (EndPoint)ipEndPoint;
|
|
|
|
// Associate this simulator's socket with the given ip/port and start listening
|
|
Connection.Connect(endPoint);
|
|
Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null);
|
|
|
|
// Send the UseCircuitCode packet to initiate the connection
|
|
Packet packet = Packets.Network.UseCircuitCode(Protocol, Network.AgentID,
|
|
Network.SessionID, CircuitCode);
|
|
|
|
// Send the initial packet out
|
|
SendPacket(packet, true);
|
|
|
|
// Track the current time for timeout purposes
|
|
DateTime start = DateTime.Now;
|
|
TimeSpan timeTaken;
|
|
|
|
while (true)
|
|
{
|
|
timeTaken = DateTime.Now - start;
|
|
if (connected || timeTaken.Milliseconds > 5000) { return; }
|
|
|
|
Thread.Sleep(10);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Helpers.Log(e.ToString(), Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
try
|
|
{
|
|
connected = false;
|
|
|
|
// Send the CloseCircuit notice
|
|
Packet packet = new Packet("CloseCircuit", Protocol, 8);
|
|
Connection.Send(packet.Data);
|
|
|
|
// Shut the socket communication down
|
|
Connection.Shutdown(SocketShutdown.Both);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Helpers.Log(e.ToString(), Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
public void SendPacket(Packet packet, bool incrementSequence)
|
|
{
|
|
byte[] buffer;
|
|
int bytes;
|
|
|
|
if (!connected && packet.Layout.Name != "UseCircuitCode")
|
|
{
|
|
Helpers.Log("Trying to send a " + packet.Layout.Name + " packet when the socket is closed",
|
|
Helpers.LogLevel.Warning);
|
|
|
|
throw new NotConnectedException();
|
|
}
|
|
|
|
try
|
|
{
|
|
if (incrementSequence)
|
|
{
|
|
if ((packet.Data[0] & Helpers.MSG_RELIABLE) != 0)
|
|
{
|
|
NeedAckMutex.WaitOne();
|
|
if (!NeedAck.ContainsKey(packet))
|
|
{
|
|
// This packet needs an ACK, keep track of when it was sent out
|
|
NeedAck.Add(packet, Environment.TickCount);
|
|
}
|
|
NeedAckMutex.ReleaseMutex();
|
|
}
|
|
|
|
// Set the sequence number here since we are manually serializing the packet
|
|
packet.Sequence = ++Sequence;
|
|
}
|
|
|
|
// Zerocode if needed
|
|
if ((packet.Data[0] & Helpers.MSG_ZEROCODED) != 0)
|
|
{
|
|
byte[] zeroBuffer = new byte[4096];
|
|
bytes = Helpers.ZeroEncode(packet.Data, packet.Data.Length, zeroBuffer);
|
|
buffer = zeroBuffer;
|
|
}
|
|
else
|
|
{
|
|
// Normal packet, no zerocoding required
|
|
buffer = packet.Data;
|
|
bytes = buffer.Length;
|
|
}
|
|
|
|
// Append any ACKs that need to be sent out to this packet
|
|
/*if (AckOutbox.Count != 0 && incrementSequence &&
|
|
packet.Layout.Name != "PacketAck" &&
|
|
packet.Layout.Name != "LogoutRequest")
|
|
{
|
|
// Claim the mutex on the AckOutbox
|
|
AckOutboxMutex.WaitOne();
|
|
|
|
// Append each ACK needing to be sent out to this packet
|
|
foreach (uint ack in AckOutbox)
|
|
{
|
|
Array.Copy(BitConverter.GetBytes(ack), 0, zeroBuffer, zeroBytes, 4);
|
|
zeroBytes += 4;
|
|
}
|
|
|
|
// Last byte is the number of ACKs
|
|
zeroBuffer[zeroBytes] = (byte)AckOutbox.Count;
|
|
zeroBytes += 1;
|
|
|
|
AckOutbox.Clear();
|
|
|
|
// Release the mutex
|
|
AckOutboxMutex.ReleaseMutex();
|
|
|
|
// Set the flag that this packet has ACKs appended to it
|
|
zeroBuffer[0] += Helpers.MSG_APPENDED_ACKS;
|
|
}*/
|
|
|
|
Connection.Send(buffer, bytes, SocketFlags.None);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Helpers.Log(e.ToString(), Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
private void SendACK(uint id)
|
|
{
|
|
try
|
|
{
|
|
Packet packet = new Packet("PacketAck", Protocol, 13);
|
|
packet.Data[8] = 1;
|
|
Array.Copy(BitConverter.GetBytes(id), 0, packet.Data, 9, 4);
|
|
|
|
// Set the sequence number
|
|
packet.Sequence = ++Sequence;
|
|
|
|
// Bypass SendPacket and send the ACK directly
|
|
int numSent = Connection.Send(packet.Data);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Helpers.Log(e.ToString(), Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
private void OnReceivedData(IAsyncResult result)
|
|
{
|
|
Packet packet;
|
|
|
|
try
|
|
{
|
|
RecvBufferMutex.WaitOne();
|
|
|
|
// If we're receiving data the sim connection is open
|
|
connected = true;
|
|
|
|
// Retrieve the incoming packet
|
|
int numBytes = Connection.EndReceiveFrom(result, ref endPoint);
|
|
|
|
if ((RecvBuffer[0] & Helpers.MSG_APPENDED_ACKS) != 0)
|
|
{
|
|
// Grab the ACKs that are appended to this packet
|
|
byte numAcks = RecvBuffer[numBytes - 1];
|
|
|
|
Helpers.Log("Found " + numAcks + " appended acks", Helpers.LogLevel.Info);
|
|
|
|
NeedAckMutex.WaitOne();
|
|
for (int i = 1; i <= numAcks; ++i)
|
|
{
|
|
ushort ack = (ushort)BitConverter.ToUInt32(RecvBuffer, numBytes - i * 4 - 1);
|
|
|
|
Beginning:
|
|
ICollection reliablePackets = NeedAck.Keys;
|
|
|
|
foreach (Packet reliablePacket in reliablePackets)
|
|
{
|
|
if (reliablePacket.Sequence == ack)
|
|
{
|
|
NeedAck.Remove(reliablePacket);
|
|
goto Beginning;
|
|
}
|
|
}
|
|
}
|
|
NeedAckMutex.ReleaseMutex();
|
|
|
|
// Adjust the packet length
|
|
numBytes = numBytes - numAcks * 4 - 1;
|
|
}
|
|
|
|
// Zerodecode this packet if necessary
|
|
if ((RecvBuffer[0] & Helpers.MSG_ZEROCODED) != 0)
|
|
{
|
|
// Allocate a temporary buffer for the zerocoded packet
|
|
byte[] zeroBuffer = new byte[4096];
|
|
int zeroBytes = Helpers.ZeroDecode(RecvBuffer, numBytes, zeroBuffer);
|
|
numBytes = zeroBytes;
|
|
packet = new Packet(zeroBuffer, numBytes, Protocol);
|
|
}
|
|
else
|
|
{
|
|
// Create the packet object from our byte array
|
|
packet = new Packet(RecvBuffer, numBytes, Protocol);
|
|
}
|
|
|
|
// Start listening again since we're done with RecvBuffer
|
|
Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null);
|
|
|
|
RecvBufferMutex.ReleaseMutex();
|
|
|
|
// Track the sequence number for this packet if it's marked as reliable
|
|
if ((packet.Data[0] & Helpers.MSG_RELIABLE) != 0)
|
|
{
|
|
// Send the ACK for this packet
|
|
// TODO: If we can make it stable, go back to the periodic ACK system
|
|
SendACK((uint)packet.Sequence);
|
|
|
|
// Check if we already received this packet
|
|
InboxMutex.WaitOne();
|
|
if (Inbox.Contains(packet.Sequence))
|
|
{
|
|
Helpers.Log("Received a duplicate " + packet.Layout.Name + ", sequence=" +
|
|
packet.Sequence + ", resent=" +
|
|
(((packet.Data[0] & Helpers.MSG_RESENT) != 0) ? "Yes" : "No"),
|
|
Helpers.LogLevel.Info);
|
|
|
|
// Avoid firing a callback twice for the same packet
|
|
InboxMutex.ReleaseMutex();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Inbox.Add(packet.Sequence, packet);
|
|
}
|
|
InboxMutex.ReleaseMutex();
|
|
}
|
|
|
|
if (packet.Layout.Name == null)
|
|
{
|
|
Helpers.Log("Received an unrecognized packet", Helpers.LogLevel.Warning);
|
|
return;
|
|
}
|
|
else if (packet.Layout.Name == "PacketAck")
|
|
{
|
|
// PacketAck is handled directly instead of using a callback to simplify access to
|
|
// the NeedAck hashtable and its mutex
|
|
ArrayList blocks = packet.Blocks();
|
|
|
|
NeedAckMutex.WaitOne();
|
|
foreach (Block block in blocks)
|
|
{
|
|
foreach (Field field in block.Fields)
|
|
{
|
|
ICollection reliablePackets = NeedAck.Keys;
|
|
|
|
// Remove this packet if it exists
|
|
foreach (Packet reliablePacket in reliablePackets)
|
|
{
|
|
if ((uint)reliablePacket.Sequence == (uint)field.Data)
|
|
{
|
|
NeedAck.Remove(reliablePacket);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
NeedAckMutex.ReleaseMutex();
|
|
}
|
|
|
|
if (Callbacks.ContainsKey(packet.Layout.Name))
|
|
{
|
|
ArrayList callbackArray = (ArrayList)Callbacks[packet.Layout.Name];
|
|
|
|
// Fire any registered callbacks
|
|
foreach (PacketCallback callback in callbackArray)
|
|
{
|
|
if (callback != null)
|
|
{
|
|
callback(packet, this);
|
|
}
|
|
}
|
|
}
|
|
else if (Callbacks.ContainsKey("Default"))
|
|
{
|
|
ArrayList callbackArray = (ArrayList)Callbacks["Default"];
|
|
|
|
// Fire any registered callbacks
|
|
foreach (PacketCallback callback in callbackArray)
|
|
{
|
|
if (callback != null)
|
|
{
|
|
callback(packet, this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Helpers.Log(e.ToString(), Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class NetworkManager
|
|
{
|
|
public LLUUID AgentID;
|
|
public LLUUID SessionID;
|
|
public string LoginError;
|
|
public Simulator CurrentSim;
|
|
public Hashtable LoginValues;
|
|
|
|
private Hashtable Callbacks;
|
|
private SecondLife Client;
|
|
private ProtocolManager Protocol;
|
|
private ArrayList Simulators;
|
|
private Mutex SimulatorsMutex;
|
|
|
|
public NetworkManager(SecondLife client, ProtocolManager protocol)
|
|
{
|
|
Client = client;
|
|
Protocol = protocol;
|
|
Simulators = new ArrayList();
|
|
SimulatorsMutex = new Mutex(false, "SimulatorsMutex");
|
|
Callbacks = new Hashtable();
|
|
CurrentSim = null;
|
|
LoginValues = null;
|
|
|
|
// Register the internal callbacks
|
|
RegisterCallback("RegionHandshake", new PacketCallback(RegionHandshakeHandler));
|
|
RegisterCallback("StartPingCheck", new PacketCallback(StartPingCheckHandler));
|
|
RegisterCallback("ParcelOverlay", new PacketCallback(ParcelOverlayHandler));
|
|
RegisterCallback("EnableSimulator", new PacketCallback(EnableSimulatorHandler));
|
|
}
|
|
|
|
public void RegisterCallback(string packet, PacketCallback callback)
|
|
{
|
|
if (!Callbacks.ContainsKey(packet))
|
|
{
|
|
Callbacks[packet] = new ArrayList();
|
|
}
|
|
|
|
ArrayList callbackArray = (ArrayList)Callbacks[packet];
|
|
callbackArray.Add(callback);
|
|
}
|
|
|
|
public void UnregisterCallback(string packet, PacketCallback callback)
|
|
{
|
|
if (!Callbacks.ContainsKey(packet))
|
|
{
|
|
Helpers.Log("Trying to unregister a callback for packet " + packet +
|
|
" when no callbacks are setup for that packet", Helpers.LogLevel.Info);
|
|
return;
|
|
}
|
|
|
|
ArrayList callbackArray = (ArrayList)Callbacks[packet];
|
|
|
|
if (callbackArray.Contains(callback))
|
|
{
|
|
callbackArray.Remove(callback);
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("Trying to unregister a non-existant callback for packet " + packet,
|
|
Helpers.LogLevel.Info);
|
|
}
|
|
}
|
|
|
|
public void SendPacket(Packet packet)
|
|
{
|
|
if (CurrentSim != null)
|
|
{
|
|
CurrentSim.SendPacket(packet, true);
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("Trying to send a packet when there is no current simulator", Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
public void SendPacket(Packet packet, Simulator simulator)
|
|
{
|
|
simulator.SendPacket(packet, true);
|
|
}
|
|
|
|
public static Hashtable DefaultLoginValues(string firstName, string lastName, string password,
|
|
string userAgent, string author)
|
|
{
|
|
return DefaultLoginValues(firstName, lastName, password, "00:00:00:00:00:00", "last",
|
|
1, 50, 50, 50, "Win", "0", userAgent, author);
|
|
}
|
|
|
|
public static Hashtable DefaultLoginValues(string firstName, string lastName, string password, string mac,
|
|
string startLocation, int major, int minor, int patch, int build, string platform, string viewerDigest,
|
|
string userAgent, string author)
|
|
{
|
|
Hashtable values = new Hashtable();
|
|
|
|
// Generate an MD5 hash of the password
|
|
MD5 md5 = new MD5CryptoServiceProvider();
|
|
byte[] hash = md5.ComputeHash(Encoding.ASCII.GetBytes(password));
|
|
StringBuilder passwordDigest = new StringBuilder();
|
|
// Convert the hash to a hex string
|
|
foreach(byte b in hash)
|
|
{
|
|
passwordDigest.AppendFormat("{0:x2}", b);
|
|
}
|
|
|
|
values["first"] = firstName;
|
|
values["last"] = lastName;
|
|
values["passwd"] = "$1$" + passwordDigest;
|
|
values["start"] = startLocation;
|
|
values["major"] = major;
|
|
values["minor"] = minor;
|
|
values["patch"] = patch;
|
|
values["build"] = build;
|
|
values["platform"] = platform;
|
|
values["mac"] = mac;
|
|
values["agree_to_tos"] = "true";
|
|
values["viewer_digest"] = viewerDigest;
|
|
values["user-agent"] = userAgent + " (" + Helpers.VERSION + ")";
|
|
values["author"] = author;
|
|
|
|
return values;
|
|
}
|
|
|
|
public bool Login(Hashtable loginParams)
|
|
{
|
|
return Login(loginParams, "https://login.agni.lindenlab.com/cgi-bin/login.cgi");
|
|
}
|
|
|
|
public bool Login(Hashtable loginParams, string url)
|
|
{
|
|
XmlRpcResponse result;
|
|
XmlRpcRequest xmlrpc = new XmlRpcRequest();
|
|
xmlrpc.MethodName = "login_to_simulator";
|
|
xmlrpc.Params.Clear();
|
|
xmlrpc.Params.Add(loginParams);
|
|
|
|
try
|
|
{
|
|
result = (XmlRpcResponse)xmlrpc.Send(url);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Helpers.Log(e.ToString(), Helpers.LogLevel.Error);
|
|
LoginError = e.Message;
|
|
LoginValues = null;
|
|
return false;
|
|
}
|
|
|
|
if (result.IsFault)
|
|
{
|
|
Helpers.Log("Fault " + result.FaultCode + ": " + result.FaultString, Helpers.LogLevel.Error);
|
|
LoginError = "Fault " + result.FaultCode + ": " + result.FaultString;
|
|
LoginValues = null;
|
|
return false;
|
|
}
|
|
|
|
LoginValues = (Hashtable)result.Value;
|
|
|
|
if ((string)LoginValues["login"] == "false")
|
|
{
|
|
LoginError = LoginValues["reason"] + ": " + LoginValues["message"];
|
|
return false;
|
|
}
|
|
|
|
System.Text.RegularExpressions.Regex LLSDtoJSON =
|
|
new System.Text.RegularExpressions.Regex(@"('|r([0-9])|r(\-))");
|
|
string json;
|
|
IDictionary jsonObject = null;
|
|
LLVector3d vector = null;
|
|
LLVector3d posVector = null;
|
|
LLVector3d lookatVector = null;
|
|
U64 regionHandle = null;
|
|
|
|
if (LoginValues.Contains("look_at"))
|
|
{
|
|
// Replace LLSD variables with object representations
|
|
|
|
// Convert LLSD string to JSON
|
|
json = "{vector:" + LLSDtoJSON.Replace((string)LoginValues["look_at"], "$2") + "}";
|
|
|
|
// Convert JSON string to a JSON object
|
|
jsonObject = JsonFacade.fromJSON(json);
|
|
JSONArray jsonVector = (JSONArray)jsonObject["vector"];
|
|
|
|
// Convert the JSON object to an LLVector3d
|
|
vector = new LLVector3d(Convert.ToDouble(jsonVector[0]),
|
|
Convert.ToDouble(jsonVector[1]), Convert.ToDouble(jsonVector[2]));
|
|
|
|
LoginValues["look_at"] = vector;
|
|
}
|
|
|
|
Hashtable home = null;
|
|
|
|
if (LoginValues.Contains("home"))
|
|
{
|
|
// Convert LLSD string to JSON
|
|
json = LLSDtoJSON.Replace((string)LoginValues["home"], "$2");
|
|
|
|
// Convert JSON string to an object
|
|
jsonObject = JsonFacade.fromJSON(json);
|
|
|
|
// Create the position vector
|
|
JSONArray array = (JSONArray)jsonObject["position"];
|
|
posVector = new LLVector3d(Convert.ToDouble(array[0]), Convert.ToDouble(array[1]),
|
|
Convert.ToDouble(array[2]));
|
|
|
|
// Create the look_at vector
|
|
array = (JSONArray)jsonObject["look_at"];
|
|
lookatVector = new LLVector3d(Convert.ToDouble(array[0]),
|
|
Convert.ToDouble(array[1]), Convert.ToDouble(array[2]));
|
|
|
|
// Create the regionhandle U64
|
|
array = (JSONArray)jsonObject["region_handle"];
|
|
regionHandle = new U64((int)array[0], (int)array[1]);
|
|
|
|
// Create a hashtable to hold the home values
|
|
home = new Hashtable();
|
|
home["position"] = posVector;
|
|
home["look_at"] = lookatVector;
|
|
home["region_handle"] = regionHandle;
|
|
|
|
LoginValues["home"] = home;
|
|
}
|
|
|
|
AgentID = new LLUUID((string)LoginValues["agent_id"]);
|
|
SessionID = new LLUUID((string)LoginValues["session_id"]);
|
|
Client.Avatar.ID = new LLUUID((string)LoginValues["agent_id"]);
|
|
Client.Avatar.FirstName = (string)LoginValues["first_name"];
|
|
Client.Avatar.LastName = (string)LoginValues["last_name"];
|
|
Client.Avatar.LookAt = vector;
|
|
Client.Avatar.HomePosition = posVector;
|
|
Client.Avatar.HomeLookAt = lookatVector;
|
|
uint circuitCode = (uint)(int)LoginValues["circuit_code"];
|
|
|
|
// Connect to the sim given in the login reply
|
|
Simulator simulator = new Simulator(Client, this.Callbacks, circuitCode,
|
|
IPAddress.Parse((string)LoginValues["sim_ip"]), (int)LoginValues["sim_port"]);
|
|
if (!simulator.Connected)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Set the current region
|
|
Client.CurrentRegion = simulator.Region;
|
|
|
|
// Simulator is successfully connected, add it to the list and set it as default
|
|
SimulatorsMutex.WaitOne();
|
|
Simulators.Add(simulator);
|
|
SimulatorsMutex.ReleaseMutex();
|
|
CurrentSim = simulator;
|
|
|
|
// Move our agent in to the sim to complete the connection
|
|
Packet packet = Packets.Sim.CompleteAgentMovement(Protocol, AgentID, SessionID, circuitCode);
|
|
SendPacket(packet, simulator);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool Connect(IPAddress ip, ushort port, uint circuitCode, bool setDefault)
|
|
{
|
|
Simulator simulator = new Simulator(Client, this.Callbacks, circuitCode,
|
|
ip, (int)port);
|
|
|
|
if (!simulator.Connected)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SimulatorsMutex.WaitOne();
|
|
Simulators.Add(simulator);
|
|
SimulatorsMutex.ReleaseMutex();
|
|
|
|
if (setDefault)
|
|
{
|
|
CurrentSim = simulator;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void Disconnect(Simulator simulator)
|
|
{
|
|
// FIXME: Properly handle disconnects
|
|
/*
|
|
if (circuit == CurrentCircuit)
|
|
{
|
|
Helpers.Log("Disconnecting simulator " + simulator.IPEndPoint.ToString(), Helpers.LogLevel.Info);
|
|
|
|
circuit.CloseCircuit();
|
|
|
|
CircuitsMutex.WaitOne();
|
|
Circuits.Remove(circuit);
|
|
if (Circuits.Count > 0)
|
|
{
|
|
CurrentCircuit = (Circuit)Circuits[0];
|
|
Helpers.Log("Switched current simulator to " + CurrentCircuit.ipEndPoint.ToString(),
|
|
Helpers.LogLevel.Info);
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("Last circuit disconnected, no open connections left", Helpers.LogLevel.Info);
|
|
CurrentCircuit = null;
|
|
}
|
|
CircuitsMutex.ReleaseMutex();
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("Disconnecting circuit " + circuit.ipEndPoint.ToString(), Helpers.LogLevel.Info);
|
|
|
|
circuit.CloseCircuit();
|
|
CircuitsMutex.WaitOne();
|
|
Circuits.Remove(circuit);
|
|
CircuitsMutex.ReleaseMutex();
|
|
return;
|
|
}
|
|
|
|
//Helpers.Log("Disconnect called with invalid circuit code " + circuitCode, Helpers.LogLevel.Warning);
|
|
*/
|
|
}
|
|
|
|
public void Logout()
|
|
{
|
|
try
|
|
{
|
|
Beginning:
|
|
// Disconnect all simulators except the current one
|
|
SimulatorsMutex.WaitOne();
|
|
if (Simulators.Count > 1)
|
|
{
|
|
foreach (Simulator simulator in Simulators)
|
|
{
|
|
if (simulator.CircuitCode != CurrentSim.CircuitCode)
|
|
{
|
|
Disconnect(simulator);
|
|
goto Beginning;
|
|
}
|
|
}
|
|
}
|
|
|
|
Packet packet = Packets.Network.LogoutRequest(Protocol, AgentID, SessionID);
|
|
CurrentSim.SendPacket(packet, true);
|
|
|
|
Simulators.Clear();
|
|
CurrentSim = null;
|
|
SimulatorsMutex.ReleaseMutex();
|
|
|
|
// TODO: We should probably check if the server actually received the logout request
|
|
// Instead we'll use this silly Sleep() to keep from accidentally flooding the login server
|
|
System.Threading.Thread.Sleep(1000);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Helpers.Log("Logout error: " + e.ToString(), Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
private void StartPingCheckHandler(Packet packet, Simulator simulator)
|
|
{
|
|
// Respond to the ping request
|
|
Packet pingPacket = Packets.Network.CompletePingCheck(Protocol, (byte)packet.Field("PingID"));
|
|
SendPacket(pingPacket, simulator);
|
|
}
|
|
|
|
private void RegionHandshakeHandler(Packet packet, Simulator simulator)
|
|
{
|
|
try
|
|
{
|
|
foreach (Block block in packet.Blocks())
|
|
{
|
|
foreach (Field field in block.Fields)
|
|
{
|
|
if (field.Layout.Name == "TerrainHeightRange00")
|
|
{
|
|
simulator.Region.TerrainHeightRange00 = (float)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainHeightRange01")
|
|
{
|
|
simulator.Region.TerrainHeightRange01 = (float)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainHeightRange10")
|
|
{
|
|
simulator.Region.TerrainHeightRange10 = (float)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainHeightRange11")
|
|
{
|
|
simulator.Region.TerrainHeightRange11 = (float)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainStartHeight00")
|
|
{
|
|
simulator.Region.TerrainStartHeight00 = (float)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainStartHeight01")
|
|
{
|
|
simulator.Region.TerrainStartHeight01 = (float)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainStartHeight10")
|
|
{
|
|
simulator.Region.TerrainStartHeight10 = (float)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainStartHeight11")
|
|
{
|
|
simulator.Region.TerrainStartHeight11 = (float)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "WaterHeight")
|
|
{
|
|
simulator.Region.WaterHeight = (float)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "SimOwner")
|
|
{
|
|
simulator.Region.SimOwner = (LLUUID)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainBase0")
|
|
{
|
|
simulator.Region.TerrainBase0 = (LLUUID)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainBase1")
|
|
{
|
|
simulator.Region.TerrainBase1 = (LLUUID)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainBase2")
|
|
{
|
|
simulator.Region.TerrainBase2 = (LLUUID)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainBase3")
|
|
{
|
|
simulator.Region.TerrainBase3 = (LLUUID)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainDetail0")
|
|
{
|
|
simulator.Region.TerrainDetail0 = (LLUUID)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainDetail1")
|
|
{
|
|
simulator.Region.TerrainDetail1 = (LLUUID)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainDetail2")
|
|
{
|
|
simulator.Region.TerrainDetail2 = (LLUUID)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "TerrainDetail3")
|
|
{
|
|
simulator.Region.TerrainDetail3 = (LLUUID)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "IsEstateManager")
|
|
{
|
|
simulator.Region.IsEstateManager = (bool)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "SimName")
|
|
{
|
|
byte[] byteArray = (byte[])field.Data;
|
|
simulator.Region.Name = System.Text.Encoding.UTF8.GetString(byteArray, 0, byteArray.Length).Replace("\0", "");
|
|
}
|
|
else if (field.Layout.Name == "CacheID")
|
|
{
|
|
simulator.Region.ID = (LLUUID)field.Data;
|
|
}
|
|
}
|
|
}
|
|
|
|
Helpers.Log("Received a region handshake for " + simulator.Region.Name, Helpers.LogLevel.Info);
|
|
|
|
// Send a RegionHandshakeReply
|
|
Packet replyPacket = new Packet("RegionHandshakeReply", Protocol, 12);
|
|
SendPacket(replyPacket, simulator);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Helpers.Log(e.ToString(), Helpers.LogLevel.Warning);
|
|
}
|
|
}
|
|
|
|
private void ParcelOverlayHandler(Packet packet, Simulator simulator)
|
|
{
|
|
int sequenceID = -1;
|
|
byte[] byteArray = null;
|
|
|
|
foreach (Block block in packet.Blocks())
|
|
{
|
|
foreach (Field field in block.Fields)
|
|
{
|
|
if (field.Layout.Name == "SequenceID")
|
|
{
|
|
sequenceID = (int)field.Data;
|
|
}
|
|
else if (field.Layout.Name == "Data")
|
|
{
|
|
byteArray = (byte[])field.Data;
|
|
if (byteArray.Length != 1024)
|
|
{
|
|
Helpers.Log("Received a parcel overlay packet with " + byteArray.Length + " bytes",
|
|
Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sequenceID >= 0 && sequenceID <= 3)
|
|
{
|
|
Array.Copy(byteArray, 0, simulator.Region.ParcelOverlay, sequenceID * 1024, 1024);
|
|
simulator.Region.ParcelOverlaysReceived++;
|
|
|
|
if (simulator.Region.ParcelOverlaysReceived > 3)
|
|
{
|
|
simulator.Region.ParcelOverlaysReceived = 0;
|
|
Helpers.Log("Finished building the " + simulator.Region.Name + " parcel overlay",
|
|
Helpers.LogLevel.Info);
|
|
|
|
// The int i = 0; is just there so I could break on it and check the
|
|
// value of debug
|
|
//int[,] debug = Region.ParcelOverlayToParcels(Client.CurrentRegion);
|
|
//int i = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("Parcel overlay with sequence ID of " + sequenceID + " received from " +
|
|
simulator.Region.Name, Helpers.LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
private void EnableSimulatorHandler(Packet packet, Simulator simulator)
|
|
{
|
|
// TODO: Actually connect to the simulator
|
|
|
|
// TODO: Sending ConfirmEnableSimulator completely screws things up. :-?
|
|
|
|
// Respond to the simulator connection request
|
|
//Packet replyPacket = Packets.Network.ConfirmEnableSimulator(Protocol, AgentID, SessionID);
|
|
//SendPacket(replyPacket, circuit);
|
|
}
|
|
}
|
|
}
|