/*
* 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.Timers;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Net.Sockets;
using System.Globalization;
using System.IO;
using Nwc.XmlRpc;
using Nii.JSON;
using libsecondlife.Packets;
namespace libsecondlife
{
///
/// This exception is thrown whenever a network operation is attempted
/// without a network connection.
///
public class NotConnectedException : ApplicationException { }
///
/// Capabilities is the name of the bi-directional HTTP REST protocol that
/// Second Life uses to communicate transactions such as teleporting or
/// asset transfers
///
public class Caps
{
///
/// Triggered when an event is received via the EventQueueGet capability;
///
///
///
public delegate void EventQueueCallback(string message, object body);
/// Reference to the SecondLife client this system is connected to
public SecondLife Client;
/// Reference to the region this system is connected to
public Region Region;
internal bool Dead = false;
private string Seedcaps;
private StringDictionary Capabilities = new StringDictionary();
private Thread EventThread;
private List Callbacks;
///
/// Default constructor
///
///
///
///
///
public Caps(SecondLife client, Region region, string seedcaps, List callbacks)
{
Client = client; Region = region;
this.Seedcaps = seedcaps; Callbacks = callbacks;
ArrayList req = new ArrayList();
req.Add("MapLayer");
req.Add("MapLayerGod");
req.Add("NewAgentInventory");
req.Add("EventQueueGet");
Hashtable resp = (Hashtable)LLSDRequest(seedcaps, req);
foreach (string cap in resp.Keys)
{
Client.Log("Got cap " + cap + ": " + (string)resp[cap], Helpers.LogLevel.Info);
Capabilities[cap] = (string)resp[cap];
}
if (Capabilities.ContainsKey("EventQueueGet"))
{
Client.Log("Running event queue", Helpers.LogLevel.Info);
EventThread = new Thread(new ThreadStart(EventQueue));
EventThread.Start();
}
}
private void EventQueue()
{
bool gotresp = false; long ack = 0;
string cap = Capabilities["EventQueueGet"];
while (!Dead)
{
try
{
Hashtable req = new Hashtable();
if (gotresp)
req["ack"] = ack;
else
req["ack"] = null;
req["done"] = false;
Hashtable resp = (Hashtable)LLSDRequest(cap, req);
ack = (long)resp["id"];
gotresp = true;
ArrayList events = (ArrayList)resp["events"];
foreach (Hashtable evt in events)
{
string msg = (string)evt["message"];
object body = (object)evt["body"];
Client.DebugLog("Event " + msg + ":" + Environment.NewLine + LLSD.LLSDDump(body, 0));
if (!Dead)
{
foreach (EventQueueCallback callback in Callbacks)
{
try { callback(msg, body); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
}
}
catch (WebException e)
{
// Perfectly normal
Client.DebugLog("EventQueueGet: " + e.Message);
}
}
}
private static object LLSDRequest(string uri, object req)
{
byte[] data = LLSD.LLSDSerialize(req);
WebRequest wreq = WebRequest.Create(uri);
wreq.Method = "POST";
wreq.ContentLength = data.Length;
Stream reqStream = wreq.GetRequestStream();
reqStream.Write(data, 0, data.Length);
reqStream.Close();
HttpWebResponse wresp = (HttpWebResponse)wreq.GetResponse();
Stream respStream = wresp.GetResponseStream();
int read; int length = 0;
byte[] respBuf = new byte[256];
do
{
read = respStream.Read(respBuf, length, 256);
if (read > 0)
{
length += read;
Array.Resize(ref respBuf, length + 256);
}
} while (read > 0);
Array.Resize(ref respBuf, length);
return LLSD.LLSDDeserialize(respBuf);
}
public void Disconnect()
{
Dead = true;
}
}
///
/// Simulator is a wrapper for a network connection to a simulator and the
/// Region class representing the block of land in the metaverse
///
public class Simulator
{
/// A public reference to the client that this Simulator object
/// is attached to
public SecondLife Client;
/// The Region class that this Simulator wraps
public Region Region;
///
/// Used internally to track sim disconnections, do not modify this
/// variable
///
public bool DisconnectCandidate = false;
///
/// The ID number associated with this particular connection to the
/// simulator, used to emulate TCP connections. This is used
/// internally for packets that have a CircuitCode field
///
public uint CircuitCode
{
get { return circuitCode; }
set { circuitCode = value; }
}
///
/// The IP address and port of the server
///
public IPEndPoint IPEndPoint
{
get { return ipEndPoint; }
}
///
/// A boolean representing whether there is a working connection to the
/// simulator or not
///
public bool Connected
{
get { return connected; }
}
private NetworkManager Network;
private Dictionary> Callbacks;
private uint Sequence = 0;
private object SequenceLock = new object();
private byte[] RecvBuffer = new byte[4096];
private byte[] ZeroBuffer = new byte[8192];
private byte[] ZeroOutBuffer = new byte[4096];
private Socket Connection = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
private AsyncCallback ReceivedData;
// Packets we sent out that need ACKs from the simulator
private Dictionary NeedAck = new Dictionary();
// Sequence numbers of packets we've received from the simulator
private Queue Inbox;
// ACKs that are queued up to be sent to the simulator
private Dictionary PendingAcks = new Dictionary();
private bool connected = false;
private uint circuitCode;
private IPEndPoint ipEndPoint;
private EndPoint endPoint;
private System.Timers.Timer AckTimer;
///
/// Constructor for Simulator
///
///
///
///
///
///
public Simulator(SecondLife client, Dictionary> callbacks,
uint circuit, IPAddress ip, int port)
{
Client = client;
Network = client.Network;
Callbacks = callbacks;
Region = new Region(client);
circuitCode = circuit;
Inbox = new Queue(Client.Settings.INBOX_SIZE);
AckTimer = new System.Timers.Timer(Client.Settings.NETWORK_TICK_LENGTH);
AckTimer.Elapsed += new ElapsedEventHandler(AckTimer_Elapsed);
// Initialize the callback for receiving a new packet
ReceivedData = new AsyncCallback(OnReceivedData);
Client.Log("Connecting to " + ip.ToString() + ":" + port, Helpers.LogLevel.Info);
try
{
// 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
UseCircuitCodePacket use = new UseCircuitCodePacket();
use.CircuitCode.Code = circuitCode;
use.CircuitCode.ID = Network.AgentID;
use.CircuitCode.SessionID = Network.SessionID;
// Start the ACK timer
AckTimer.Start();
// Send the initial packet out
SendPacket(use, true);
// Track the current time for timeout purposes
int start = Environment.TickCount;
while (true)
{
if (connected || Environment.TickCount - start > Client.Settings.SIMULATOR_TIMEOUT)
{
return;
}
System.Threading.Thread.Sleep(10);
}
}
catch (Exception e)
{
Client.Log(e.ToString(), Helpers.LogLevel.Error);
}
}
///
/// Disconnect a Simulator
///
public void Disconnect()
{
if (connected)
{
connected = false;
AckTimer.Stop();
// Send the CloseCircuit notice
CloseCircuitPacket close = new CloseCircuitPacket();
if (Connection.Connected)
{
try
{
Connection.Send(close.ToBytes());
}
catch (SocketException)
{
// There's a high probability of this failing if the network is
// disconnecting, so don't even bother logging the error
}
}
try
{
// Shut the socket communication down
Connection.Shutdown(SocketShutdown.Both);
}
catch (SocketException)
{
}
}
}
///
/// Sends a packet
///
/// Packet to be sent
/// Increment sequence number?
public void SendPacket(Packet packet, bool incrementSequence)
{
byte[] buffer;
int bytes;
if (!connected && packet.Type != PacketType.UseCircuitCode)
{
Client.Log("Trying to send a " + packet.Type.ToString() + " packet when the socket is closed",
Helpers.LogLevel.Info);
return;
}
if (packet.Header.AckList.Length > 0)
{
// Scrub any appended ACKs since all of the ACK handling is done here
packet.Header.AckList = new uint[0];
}
packet.Header.AppendedAcks = false;
// Keep track of when this packet was sent out
packet.TickCount = Environment.TickCount;
if (incrementSequence)
{
// Set the sequence number
lock (SequenceLock)
{
if (Sequence > Client.Settings.MAX_SEQUENCE)
Sequence = 1;
else
Sequence++;
packet.Header.Sequence = Sequence;
}
if (packet.Header.Reliable)
{
lock (NeedAck)
{
if (!NeedAck.ContainsKey(packet.Header.Sequence))
{
NeedAck.Add(packet.Header.Sequence, packet);
}
else
{
Client.Log("Attempted to add a duplicate sequence number (" +
packet.Header.Sequence + ") to the NeedAck dictionary for packet type " +
packet.Type.ToString(), Helpers.LogLevel.Warning);
}
}
// Don't append ACKs to resent packets, in case that's what was causing the
// delivery to fail
if (!packet.Header.Resent)
{
// Append any ACKs that need to be sent out to this packet
lock (PendingAcks)
{
if (PendingAcks.Count > 0 && PendingAcks.Count < Client.Settings.MAX_APPENDED_ACKS &&
packet.Type != PacketType.PacketAck &&
packet.Type != PacketType.LogoutRequest)
{
packet.Header.AckList = new uint[PendingAcks.Count];
int i = 0;
foreach (uint ack in PendingAcks.Values)
{
packet.Header.AckList[i] = ack;
i++;
}
PendingAcks.Clear();
packet.Header.AppendedAcks = true;
}
}
}
}
}
// Serialize the packet
buffer = packet.ToBytes();
bytes = buffer.Length;
try
{
// Zerocode if needed
if (packet.Header.Zerocoded)
{
lock (ZeroOutBuffer)
{
bytes = Helpers.ZeroEncode(buffer, bytes, ZeroOutBuffer);
Connection.Send(ZeroOutBuffer, bytes, SocketFlags.None);
}
}
else
{
Connection.Send(buffer, bytes, SocketFlags.None);
}
}
catch (SocketException)
{
Client.Log("Tried to send a " + packet.Type.ToString() + " on a closed socket",
Helpers.LogLevel.Warning);
Disconnect();
}
}
///
/// Send a raw byte array payload as a packet
///
/// The packet payload
/// Whether the second, third, and fourth bytes
/// should be modified to the current stream sequence number
public void SendPacket(byte[] payload, bool setSequence)
{
if (connected)
{
try
{
if (setSequence && payload.Length > 3)
{
lock (SequenceLock)
{
payload[1] = (byte)(Sequence >> 16);
payload[2] = (byte)(Sequence >> 8);
payload[3] = (byte)(Sequence % 256);
Sequence++;
}
}
Connection.Send(payload, payload.Length, SocketFlags.None);
}
catch (SocketException e)
{
Client.Log(e.ToString(), Helpers.LogLevel.Error);
}
}
else
{
Client.Log("Attempted to send a " + payload.Length + " byte payload when " +
"we are disconnected", Helpers.LogLevel.Warning);
}
}
///
/// Returns Simulator Name as a String
///
///
public override string ToString()
{
return Region.Name + " (" + ipEndPoint.ToString() + ")";
}
///
/// Sends out pending acknowledgements
///
private void SendAcks()
{
lock (PendingAcks)
{
if (connected && PendingAcks.Count > 0)
{
if (PendingAcks.Count > 250)
{
// FIXME: Handle the odd case where we have too many pending ACKs queued up
Client.Log("Too many ACKs queued up!", Helpers.LogLevel.Error);
return;
}
int i = 0;
PacketAckPacket acks = new PacketAckPacket();
acks.Packets = new PacketAckPacket.PacketsBlock[PendingAcks.Count];
foreach (uint ack in PendingAcks.Values)
{
acks.Packets[i] = new PacketAckPacket.PacketsBlock();
acks.Packets[i].ID = ack;
i++;
}
acks.Header.Reliable = false;
SendPacket(acks, true);
PendingAcks.Clear();
}
}
}
///
/// Resend unacknowledged packets
///
private void ResendUnacked()
{
if (connected)
{
int now = Environment.TickCount;
lock (NeedAck)
{
foreach (Packet packet in NeedAck.Values)
{
if (now - packet.TickCount > Client.Settings.RESEND_TIMEOUT)
{
Client.Log("Resending " + packet.Type.ToString() + " packet, " +
(now - packet.TickCount) + "ms have passed", Helpers.LogLevel.Info);
packet.Header.Resent = true;
SendPacket(packet, false);
}
}
}
}
}
///
/// Callback handler for incomming data
///
///
private void OnReceivedData(IAsyncResult result)
{
Packet packet = null;
int numBytes;
// If we're receiving data the sim connection is open
connected = true;
// Update the disconnect flag so this sim doesn't time out
DisconnectCandidate = false;
lock (RecvBuffer)
{
// Retrieve the incoming packet
try
{
numBytes = Connection.EndReceiveFrom(result, ref endPoint);
int packetEnd = numBytes - 1;
packet = Packet.BuildPacket(RecvBuffer, ref packetEnd, ZeroBuffer);
Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null);
}
catch (SocketException)
{
Client.Log(endPoint.ToString() + " socket is closed, shutting down " + this.Region.Name,
Helpers.LogLevel.Info);
connected = false;
Network.DisconnectSim(this);
return;
}
}
// Fail-safe check
if (packet == null)
{
Client.Log("Couldn't build a message from the incoming data", Helpers.LogLevel.Warning);
return;
}
// Track the sequence number for this packet if it's marked as reliable
if (packet.Header.Reliable)
{
if (PendingAcks.Count > Client.Settings.MAX_PENDING_ACKS)
{
SendAcks();
}
// Check if we already received this packet
if (Inbox.Contains(packet.Header.Sequence))
{
Client.Log("Received a duplicate " + packet.Type.ToString() + ", sequence=" +
packet.Header.Sequence + ", resent=" + ((packet.Header.Resent) ? "Yes" : "No") +
", Inbox.Count=" + Inbox.Count + ", NeedAck.Count=" + NeedAck.Count,
Helpers.LogLevel.Info);
// Send an ACK for this packet immediately
//SendAck(packet.Header.Sequence);
// TESTING: Try just queuing up ACKs for resent packets instead of immediately triggering an ACK
lock (PendingAcks)
{
uint sequence = (uint)packet.Header.Sequence;
if (!PendingAcks.ContainsKey(sequence)) { PendingAcks[sequence] = sequence; }
}
// Avoid firing a callback twice for the same packet
return;
}
else
{
lock (PendingAcks)
{
uint sequence = (uint)packet.Header.Sequence;
if (!PendingAcks.ContainsKey(sequence)) { PendingAcks[sequence] = sequence; }
}
}
}
// Add this packet to our inbox
lock (Inbox)
{
while (Inbox.Count >= Client.Settings.INBOX_SIZE)
{
Inbox.Dequeue();
}
Inbox.Enqueue(packet.Header.Sequence);
}
// Handle appended ACKs
if (packet.Header.AppendedAcks)
{
lock (NeedAck)
{
foreach (uint ack in packet.Header.AckList)
{
NeedAck.Remove(ack);
}
}
}
// Handle PacketAck packets
if (packet.Type == PacketType.PacketAck)
{
PacketAckPacket ackPacket = (PacketAckPacket)packet;
lock (NeedAck)
{
foreach (PacketAckPacket.PacketsBlock block in ackPacket.Packets)
{
NeedAck.Remove(block.ID);
}
}
}
// Fire the registered packet events
#region FireCallbacks
if (Callbacks.ContainsKey(packet.Type))
{
List callbackArray = Callbacks[packet.Type];
// Fire any registered callbacks
foreach (NetworkManager.PacketCallback callback in callbackArray)
{
if (callback != null)
{
try
{
callback(packet, this);
}
catch (Exception e)
{
Client.Log("Caught an exception in a packet callback: " + e.ToString(),
Helpers.LogLevel.Error);
}
}
}
}
if (Callbacks.ContainsKey(PacketType.Default))
{
List callbackArray = Callbacks[PacketType.Default];
// Fire any registered callbacks
foreach (NetworkManager.PacketCallback callback in callbackArray)
{
if (callback != null)
{
try
{
callback(packet, this);
}
catch (Exception e)
{
Client.Log("Caught an exception in a packet callback: " + e.ToString(),
Helpers.LogLevel.Error);
}
}
}
}
#endregion FireCallbacks
}
private void AckTimer_Elapsed(object sender, ElapsedEventArgs ea)
{
if (connected)
{
SendAcks();
ResendUnacked();
}
}
}
///
/// NetworkManager is responsible for managing the network layer of
/// libsecondlife. It tracks all the server connections, serializes
/// outgoing traffic and deserializes incoming traffic, and provides
/// instances of delegates for network-related events.
///
public class NetworkManager
{
///
/// Coupled with RegisterCallback(), this is triggered whenever a packet
/// of a registered type is received
///
///
///
public delegate void PacketCallback(Packet packet, Simulator simulator);
///
/// Triggered when a simulator other than the simulator that is currently
/// being occupied disconnects for whatever reason
///
/// The simulator that disconnected, which will become a null
/// reference after the callback is finished
/// Enumeration explaining the reason for the disconnect
public delegate void SimDisconnectCallback(Simulator simulator, DisconnectType reason);
///
/// Triggered when we are logged out of the grid due to a simulator request,
/// client request, network timeout, or any other cause
///
/// Enumeration explaining the reason for the disconnect
/// If we were logged out by the simulator, this
/// is a message explaining why
public delegate void DisconnectCallback(DisconnectType reason, string message);
///
/// Triggered when CurrentSim changes
///
/// A reference to the old value of CurrentSim
public delegate void CurrentSimChangedCallback(Simulator PreviousSimulator);
///
/// Explains why a simulator or the grid disconnected from us
///
public enum DisconnectType
{
/// The client requested the logout or simulator disconnect
ClientInitiated,
/// The server notified us that it is disconnecting
ServerInitiated,
/// Either a socket was closed or network traffic timed out
NetworkTimeout
}
///
/// The permanent UUID for the logged in avatar
///
public LLUUID AgentID;
///
/// A temporary UUID assigned to this session, used for secure
/// transactions
///
public LLUUID SessionID;
///
/// A string holding a descriptive error on login failure, empty
/// otherwise
///
public string LoginError;
///
/// The simulator that the logged in avatar is currently occupying
///
public Simulator CurrentSim;
///
/// The capabilities for the current simulator
///
public Caps CurrentCaps;
///
/// The complete dictionary of all the login values returned by the
/// RPC login server, converted to native data types wherever possible
///
public Dictionary LoginValues = new Dictionary();
///
/// Shows whether the network layer is logged in to the grid or not
///
public bool Connected
{
get { return connected; }
}
///
/// An event for the connection to a simulator other than the currently
/// occupied one disconnecting
///
public event SimDisconnectCallback OnSimDisconnected;
///
/// An event for being logged out either through client request, server
/// forced, or network error
///
public event DisconnectCallback OnDisconnected;
///
/// An event for when CurrentSim changes
///
public event CurrentSimChangedCallback OnCurrentSimChanged;
private SecondLife Client;
private Dictionary> Callbacks = new Dictionary>();
private List Simulators = new List();
private System.Timers.Timer DisconnectTimer;
private System.Timers.Timer LogoutTimer;
private bool connected;
private List EventQueueCallbacks = new List();
private const int NetworkTrafficTimeout = 15000;
ManualResetEvent LogoutReplyEvent = new ManualResetEvent(false);
///
///
///
///
public NetworkManager(SecondLife client)
{
Client = client;
CurrentSim = null;
// Register the internal callbacks
RegisterCallback(PacketType.RegionHandshake, new PacketCallback(RegionHandshakeHandler));
RegisterCallback(PacketType.StartPingCheck, new PacketCallback(StartPingCheckHandler));
RegisterCallback(PacketType.ParcelOverlay, new PacketCallback(ParcelOverlayHandler));
RegisterCallback(PacketType.EnableSimulator, new PacketCallback(EnableSimulatorHandler));
RegisterCallback(PacketType.KickUser, new PacketCallback(KickUserHandler));
RegisterCallback(PacketType.LogoutReply, new PacketCallback(LogoutReplyHandler));
// Disconnect a sim if we exceed our threshold
DisconnectTimer = new System.Timers.Timer(NetworkTrafficTimeout);
DisconnectTimer.Elapsed += new ElapsedEventHandler(DisconnectTimer_Elapsed);
}
///
///
///
///
///
public void RegisterCallback(PacketType type, PacketCallback callback)
{
if (!Callbacks.ContainsKey(type))
{
Callbacks[type] = new List();
}
List callbackArray = Callbacks[type];
callbackArray.Add(callback);
}
///
///
///
///
///
public void UnregisterCallback(PacketType type, PacketCallback callback)
{
if (!Callbacks.ContainsKey(type))
{
Client.Log("Trying to unregister a callback for packet " + type.ToString() +
" when no callbacks are setup for that packet", Helpers.LogLevel.Info);
return;
}
List callbackArray = Callbacks[type];
if (callbackArray.Contains(callback))
{
callbackArray.Remove(callback);
}
else
{
Client.Log("Trying to unregister a non-existant callback for packet " + type.ToString(),
Helpers.LogLevel.Info);
}
}
///
///
///
///
public void RegisterEventCallback(Caps.EventQueueCallback callback)
{
EventQueueCallbacks.Add(callback);
}
///
///
///
///
public void SendPacket(Packet packet)
{
if (CurrentSim != null && CurrentSim.Connected)
{
CurrentSim.SendPacket(packet, true);
}
}
///
///
///
///
///
public void SendPacket(Packet packet, Simulator simulator)
{
if (simulator != null && simulator.Connected)
{
simulator.SendPacket(packet, true);
}
}
///
///
///
///
/// Whether to set the second, third, and fourth
/// bytes of the payload to the current sequence number
public void SendPacket(byte[] payload, bool setSequence)
{
if (connected && CurrentSim != null)
{
CurrentSim.SendPacket(payload, setSequence);
}
else
{
Client.Log("Trying to send a " + payload.Length + " payload " +
"when we're not connected", Helpers.LogLevel.Warning);
}
}
///
/// Use this if you want to login to a specific location
///
///
///
///
///
/// string with a value that can be used in the start field in .DefaultLoginValues()
public static string StartLocation(string sim, int x, int y, int z)
{
//uri:sim&x&y&z
return "uri:" + sim.ToLower() + "&" + x + "&" + y + "&" + z;
}
///
///
///
///
///
///
///
///
///
public Dictionary 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, false);
}
///
///
///
///
///
///
///
///
///
public Dictionary DefaultLoginValues(string firstName, string lastName,
string password, string startLocation, string userAgent, string author, bool md5pass)
{
return DefaultLoginValues(firstName, lastName, password, "00:00:00:00:00:00", startLocation,
1, 50, 50, 50, "Win", "0", userAgent, author, md5pass);
}
///
///
///
///
///
///
///
///
///
///
///
///
///
public Dictionary DefaultLoginValues(string firstName, string lastName,
string password, string mac, string startLocation, string platform,
string viewerDigest, string userAgent, string author)
{
return DefaultLoginValues(firstName, lastName, password, mac, startLocation,
1, 50, 50, 50, platform, viewerDigest, userAgent, author, false);
}
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
public Dictionary 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,
bool md5pass)
{
Dictionary values = new Dictionary();
values["first"] = firstName;
values["last"] = lastName;
values["passwd"] = md5pass ? password : Helpers.MD5(password);
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["read_critical"] = "true";
values["viewer_digest"] = viewerDigest;
values["user-agent"] = userAgent + " (" + Client.Settings.VERSION + ")";
values["author"] = author;
// Build the options array
List