* Added fields in Avatar to hold the parsed data from login * CoarseLocationUpdate packets handled internally * Added Network.LoginValues hashtable, removed second parameter from .Login() * Updated examples to reflect LoginValues / Login() change git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@39 52acb1d6-8a22-11de-b505-999d5b087335
1250 lines
35 KiB
C#
1250 lines
35 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.Net;
|
|
using System.Collections;
|
|
|
|
namespace libsecondlife
|
|
{
|
|
public struct Field
|
|
{
|
|
public object Data;
|
|
public MapField Layout;
|
|
}
|
|
|
|
public struct Block
|
|
{
|
|
public ArrayList Fields;
|
|
public MapBlock Layout;
|
|
}
|
|
|
|
public class Packet
|
|
{
|
|
public byte[] Data;
|
|
public MapPacket Layout;
|
|
public ushort Sequence
|
|
{
|
|
get
|
|
{
|
|
short sequence = BitConverter.ToInt16(Data, 2);
|
|
// TODO: To support big endian platforms, we need to replace NetworkToHostOrder with
|
|
// a big endian to little endian function
|
|
return (ushort)IPAddress.NetworkToHostOrder(sequence);
|
|
}
|
|
|
|
set
|
|
{
|
|
byte[] sequence = BitConverter.GetBytes(value);
|
|
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
Data[2] = sequence[1];
|
|
Data[3] = sequence[0];
|
|
}
|
|
else
|
|
{
|
|
Data[2] = sequence[0];
|
|
Data[3] = sequence[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
private ProtocolManager Protocol;
|
|
|
|
public Packet(string command, ProtocolManager protocol, int length)
|
|
{
|
|
Protocol = protocol;
|
|
Data = new byte[length];
|
|
Layout = protocol.Command(command);
|
|
|
|
if (Layout == null)
|
|
{
|
|
Helpers.Log("Attempting to build a packet with invalid command \"" + command + "\"",
|
|
Helpers.LogLevel.Error);
|
|
}
|
|
|
|
switch (Layout.Frequency)
|
|
{
|
|
case PacketFrequency.Low:
|
|
// Set the low frequency identifier bits
|
|
byte[] lowHeader = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF};
|
|
Array.Copy(lowHeader, 0, Data, 0, 6);
|
|
// Store the packet ID in big endian format
|
|
short id = IPAddress.HostToNetworkOrder((short)Layout.ID);
|
|
Array.Copy(BitConverter.GetBytes(id), 0, Data, 6, 2);
|
|
break;
|
|
case PacketFrequency.Medium:
|
|
// Set the medium frequency identifier bit
|
|
byte[] mediumHeader = {0x00, 0x00, 0x00, 0x00, 0xFF};
|
|
Array.Copy(mediumHeader, 0, Data, 0, 5);
|
|
Data[5] = (byte)Layout.ID;
|
|
break;
|
|
case PacketFrequency.High:
|
|
byte[] highHeader = {0x00, 0x00, 0x00, 0x00};
|
|
Array.Copy(highHeader, 0, Data, 0, 4);
|
|
Data[4] = (byte)Layout.ID;
|
|
break;
|
|
}
|
|
}
|
|
|
|
public Packet(byte[] data, int length, ProtocolManager protocol)
|
|
{
|
|
Protocol = protocol;
|
|
ushort command;
|
|
Data = new byte[length];
|
|
|
|
if (length < 5)
|
|
{
|
|
Helpers.Log("Received a packet with less than 5 bytes", Helpers.LogLevel.Warning);
|
|
|
|
// Create an empty MapPacket
|
|
Layout = new MapPacket();
|
|
Layout.Blocks = new ArrayList();
|
|
|
|
return;
|
|
}
|
|
|
|
if (data[4] == 0xFF)
|
|
{
|
|
if ((byte)data[5] == 0xFF)
|
|
{
|
|
// Low frequency
|
|
command = BitConverter.ToUInt16(data, 6);
|
|
command = (ushort)IPAddress.NetworkToHostOrder((short)command);
|
|
|
|
Layout = protocol.Command(command, PacketFrequency.Low);
|
|
}
|
|
else
|
|
{
|
|
// Medium frequency
|
|
command = (ushort)data[5];
|
|
Layout = protocol.Command(command, PacketFrequency.Medium);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// High frequency
|
|
command = (ushort)data[4];
|
|
Layout = protocol.Command(command, PacketFrequency.High);
|
|
}
|
|
|
|
if (Layout == null)
|
|
{
|
|
Helpers.Log("Received a packet with an unknown command ID", Helpers.LogLevel.Warning);
|
|
|
|
// Create an empty MapPacket
|
|
Layout = new MapPacket();
|
|
Layout.Blocks = new ArrayList();
|
|
}
|
|
|
|
// Copy the network byte array to this packet's byte array
|
|
Array.Copy(data, 0, Data, 0, length);
|
|
}
|
|
|
|
public ArrayList Blocks()
|
|
{
|
|
Field field;
|
|
Block block;
|
|
byte blockCount;
|
|
int fieldSize;
|
|
|
|
// Get the starting position of the SL payload (different than the UDP payload)
|
|
int pos = HeaderLength();
|
|
|
|
// Initialize the block list we are returning
|
|
ArrayList blocks = new ArrayList();
|
|
|
|
foreach (MapBlock blockMap in Layout.Blocks)
|
|
{
|
|
if (blockMap.Count == -1)
|
|
{
|
|
// Variable count block
|
|
if (pos < Data.Length)
|
|
{
|
|
blockCount = Data[pos];
|
|
pos++;
|
|
}
|
|
else
|
|
|
|
{
|
|
Helpers.Log("getBlocks(): goto 1 reached", Helpers.LogLevel.Warning);
|
|
goto Done;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
blockCount = (byte)blockMap.Count;
|
|
}
|
|
|
|
for (int i = 0; i < blockCount; ++i)
|
|
{
|
|
// Create a new block to push back on the list
|
|
block = new Block();
|
|
block.Layout = blockMap;
|
|
block.Fields = new ArrayList();
|
|
|
|
foreach (MapField fieldMap in blockMap.Fields)
|
|
{
|
|
if (fieldMap.Type == FieldType.Variable)
|
|
{
|
|
if (fieldMap.Count == 1)
|
|
{
|
|
// Field length described with one byte
|
|
if (pos < Data.Length)
|
|
{
|
|
fieldSize = (ushort)Data[pos];
|
|
pos++;
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("getBlocks(): goto 2 reached", Helpers.LogLevel.Warning);
|
|
goto BlockDone;
|
|
}
|
|
}
|
|
else // (fieldMap.Count == 2)
|
|
{
|
|
// Field length described with two bytes
|
|
if (pos + 1 < Data.Length)
|
|
{
|
|
fieldSize = BitConverter.ToUInt16(Data, pos);
|
|
pos += 2;
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("getBlocks(): goto 3 reached", Helpers.LogLevel.Warning);
|
|
goto BlockDone;
|
|
}
|
|
}
|
|
|
|
if (fieldSize != 0)
|
|
{
|
|
if (pos + fieldSize <= Data.Length)
|
|
{
|
|
// Create a new field to add to the fields for this block
|
|
field = new Field();
|
|
field.Data = GetField(Data, pos, fieldMap.Type, fieldSize);
|
|
field.Layout = fieldMap;
|
|
|
|
block.Fields.Add(field);
|
|
|
|
pos += fieldSize;
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("getBlocks(): goto 4 reached", Helpers.LogLevel.Warning);
|
|
goto BlockDone;
|
|
}
|
|
}
|
|
}
|
|
else if (fieldMap.Type == FieldType.Fixed)
|
|
{
|
|
fieldSize = fieldMap.Count;
|
|
|
|
if (pos + fieldSize <= Data.Length)
|
|
{
|
|
// Create a new field to add to the fields for this block
|
|
field = new Field();
|
|
field.Data = GetField(Data, pos, fieldMap.Type, fieldSize);
|
|
field.Layout = fieldMap;
|
|
|
|
block.Fields.Add(field);
|
|
|
|
pos += fieldSize;
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("getBlocks(): goto 4 reached", Helpers.LogLevel.Warning);
|
|
goto BlockDone;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int j = 0; j < fieldMap.Count; ++j)
|
|
{
|
|
fieldSize = (int)Protocol.TypeSizes[fieldMap.Type];
|
|
|
|
if (pos + fieldSize <= Data.Length)
|
|
{
|
|
// Create a new field to add to the fields for this block
|
|
field = new Field();
|
|
field.Data = GetField(Data, pos, fieldMap.Type, fieldSize);
|
|
field.Layout = fieldMap;
|
|
|
|
block.Fields.Add(field);
|
|
|
|
pos += fieldSize;
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("getBlocks(): goto 5 reached", Helpers.LogLevel.Warning);
|
|
goto BlockDone;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BlockDone:
|
|
blocks.Add(block);
|
|
}
|
|
}
|
|
|
|
Done:
|
|
return blocks;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
string output = "";
|
|
ArrayList blocks = Blocks();
|
|
|
|
output += "---- " + Layout.Name + " ----\n";
|
|
|
|
foreach (Block block in blocks)
|
|
{
|
|
output += block.Layout.Name + "\n";
|
|
|
|
foreach (Field field in block.Fields)
|
|
{
|
|
output += " " + field.Layout.Name + ": " + field.Data.ToString() + "\n";
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
private object GetField(byte[] byteArray, int pos, FieldType type, int fieldSize)
|
|
{
|
|
switch (type)
|
|
{
|
|
case FieldType.U8:
|
|
return byteArray[pos];
|
|
case FieldType.U16:
|
|
return BitConverter.ToUInt16(byteArray, pos);
|
|
case FieldType.U32:
|
|
return BitConverter.ToUInt32(byteArray, pos);
|
|
case FieldType.U64:
|
|
return new U64(byteArray, pos);
|
|
case FieldType.S8:
|
|
return (sbyte)byteArray[pos];
|
|
case FieldType.S16:
|
|
return BitConverter.ToInt16(byteArray, pos);
|
|
case FieldType.S32:
|
|
return BitConverter.ToInt32(byteArray, pos);
|
|
case FieldType.S64:
|
|
return BitConverter.ToInt64(byteArray, pos);
|
|
case FieldType.F32:
|
|
return BitConverter.ToSingle(byteArray, pos);
|
|
case FieldType.F64:
|
|
return BitConverter.ToDouble(byteArray, pos);
|
|
case FieldType.LLUUID:
|
|
return new LLUUID(byteArray, pos);
|
|
case FieldType.BOOL:
|
|
return (byteArray[pos] != 0) ? (bool)true : (bool)false;
|
|
case FieldType.LLVector3:
|
|
return new LLVector3(byteArray, pos);
|
|
case FieldType.LLVector3d:
|
|
return new LLVector3d(byteArray, pos);
|
|
case FieldType.LLVector4:
|
|
return new LLVector4(byteArray, pos);
|
|
case FieldType.LLQuaternion:
|
|
return new LLQuaternion(byteArray, pos);
|
|
case FieldType.IPADDR:
|
|
uint address = BitConverter.ToUInt32(byteArray, pos);
|
|
return new IPAddress(address);
|
|
case FieldType.IPPORT:
|
|
ushort port = BitConverter.ToUInt16(byteArray, pos);
|
|
return (ushort)IPAddress.NetworkToHostOrder((short)port);
|
|
case FieldType.Variable:
|
|
case FieldType.Fixed:
|
|
byte[] bytes = new byte[fieldSize];
|
|
|
|
for (int i = 0; i < fieldSize; ++i)
|
|
{
|
|
bytes[i] = byteArray[pos + i];
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public int HeaderLength()
|
|
{
|
|
switch (Layout.Frequency)
|
|
{
|
|
case PacketFrequency.High:
|
|
return 5;
|
|
case PacketFrequency.Medium:
|
|
return 6;
|
|
case PacketFrequency.Low:
|
|
return 8;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public class PacketBuilder
|
|
{
|
|
public static Packet BuildPacket(string name, ProtocolManager protocol, Hashtable blocks)
|
|
{
|
|
Hashtable fields;
|
|
ArrayList payload = new ArrayList();
|
|
byte[] byteArray = new byte[1024];
|
|
int length = 0;
|
|
int blockCount = 0;
|
|
int fieldLength = 0;
|
|
IDictionaryEnumerator blocksEnum;
|
|
|
|
MapPacket packetMap = protocol.Command(name);
|
|
|
|
// Build the header
|
|
#region Header
|
|
switch (packetMap.Frequency)
|
|
{
|
|
case PacketFrequency.High:
|
|
byteArray[4] = (byte)packetMap.ID;
|
|
length = 5;
|
|
break;
|
|
case PacketFrequency.Medium:
|
|
byteArray[4] = 0xFF;
|
|
byteArray[5] = (byte)packetMap.ID;
|
|
length = 6;
|
|
break;
|
|
case PacketFrequency.Low:
|
|
byteArray[4] = 0xFF;
|
|
byteArray[5] = 0xFF;
|
|
//short commandID = ;
|
|
Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)packetMap.ID)), 0,
|
|
byteArray, 6, 2);
|
|
length = 8;
|
|
break;
|
|
}
|
|
#endregion Header
|
|
|
|
foreach (MapBlock blockMap in packetMap.Blocks)
|
|
{
|
|
// If this is a variable count block, count the number of appearances of this block in the
|
|
// passed in Hashtable and prepend a counter byte
|
|
#region VariableSize
|
|
if (blockMap.Count == -1)
|
|
{
|
|
blockCount = 0;
|
|
|
|
// Count the number of this type of block in the blocks Hashtable
|
|
blocksEnum = blocks.GetEnumerator();
|
|
|
|
while (blocksEnum.MoveNext())
|
|
{
|
|
if ((string)blocksEnum.Value == blockMap.Name)
|
|
{
|
|
blockCount++;
|
|
}
|
|
}
|
|
|
|
if (blockCount > 255)
|
|
{
|
|
Helpers.Log("Trying to put more than 255 blocks in a variable block position, " +
|
|
"this will not end well", Helpers.LogLevel.Error);
|
|
}
|
|
|
|
// Prepend the blocks with a count
|
|
byteArray[length] = (byte)blockCount;
|
|
length++;
|
|
}
|
|
#endregion VariablSize
|
|
|
|
// Reset blockCount
|
|
blockCount = 0;
|
|
|
|
// Check for blocks of this type in the Hashtable
|
|
#region BuildBlock
|
|
blocksEnum = blocks.GetEnumerator();
|
|
|
|
while (blocksEnum.MoveNext())
|
|
{
|
|
if ((string)blocksEnum.Value == blockMap.Name)
|
|
{
|
|
// Found a match of this block
|
|
if (blockMap.Count == -1 || blockCount < blockMap.Count)
|
|
{
|
|
blockCount++;
|
|
|
|
#region TryBlockTypecast
|
|
try
|
|
{
|
|
fields = (Hashtable)blocksEnum.Key;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
Helpers.Log("Something other than a field Hashtable was passed to BuildPacket " +
|
|
"inside of the block Hashtable", Helpers.LogLevel.Warning);
|
|
continue;
|
|
}
|
|
#endregion TryBlockTypecast
|
|
|
|
foreach (MapField fieldMap in blockMap.Fields)
|
|
{
|
|
if (fields.ContainsKey(fieldMap.Name))
|
|
{
|
|
object field = fields[fieldMap.Name];
|
|
|
|
#region AddField
|
|
try
|
|
{
|
|
switch (fieldMap.Type)
|
|
{
|
|
case FieldType.U8:
|
|
byteArray[length] = (byte)field;
|
|
length++;
|
|
break;
|
|
case FieldType.U16:
|
|
Array.Copy(BitConverter.GetBytes((ushort)field), 0, byteArray, length, 2);
|
|
length += 2;
|
|
break;
|
|
case FieldType.U32:
|
|
Array.Copy(BitConverter.GetBytes((uint)field), 0, byteArray, length, 4);
|
|
length += 4;
|
|
break;
|
|
case FieldType.U64:
|
|
Array.Copy(((U64)field).GetBytes(), 0, byteArray, length, 8);
|
|
length += 8;
|
|
break;
|
|
case FieldType.S8:
|
|
byteArray[length] = (byte)field;
|
|
length++;
|
|
break;
|
|
case FieldType.S16:
|
|
Array.Copy(BitConverter.GetBytes((short)field), 0, byteArray, length, 2);
|
|
length += 2;
|
|
break;
|
|
case FieldType.S32:
|
|
Array.Copy(BitConverter.GetBytes((int)field), 0, byteArray, length, 4);
|
|
length += 4;
|
|
break;
|
|
case FieldType.S64:
|
|
Array.Copy(BitConverter.GetBytes((long)field), 0, byteArray, length, 8);
|
|
length += 8;
|
|
break;
|
|
case FieldType.F32:
|
|
Array.Copy(BitConverter.GetBytes((float)field), 0, byteArray, length, 4);
|
|
length += 4;
|
|
break;
|
|
case FieldType.F64:
|
|
Array.Copy(BitConverter.GetBytes((double)field), 0, byteArray, length, 8);
|
|
length += 8;
|
|
break;
|
|
case FieldType.LLUUID:
|
|
Array.Copy(((LLUUID)field).Data, 0, byteArray, length, 16);
|
|
length += 16;
|
|
break;
|
|
case FieldType.BOOL:
|
|
byteArray[length] = (byte)field;
|
|
length++;
|
|
break;
|
|
case FieldType.LLVector3:
|
|
Array.Copy(((LLVector3)field).GetBytes(), 0, byteArray, length, 12);
|
|
length += 12;
|
|
break;
|
|
case FieldType.LLVector3d:
|
|
Array.Copy(((LLVector3d)field).GetBytes(), 0, byteArray, length, 24);
|
|
length += 24;
|
|
break;
|
|
case FieldType.LLVector4:
|
|
Array.Copy(((LLVector4)field).GetBytes(), 0, byteArray, length, 16);
|
|
length += 16;
|
|
break;
|
|
case FieldType.LLQuaternion:
|
|
Array.Copy(((LLQuaternion)field).GetBytes(), 0, byteArray, length, 16);
|
|
length += 16;
|
|
break;
|
|
case FieldType.IPADDR:
|
|
Array.Copy(BitConverter.GetBytes((ushort)field), 0, byteArray, length, 2);
|
|
length += 2;
|
|
break;
|
|
case FieldType.IPPORT:
|
|
Array.Copy(BitConverter.GetBytes((ushort)field), 0, byteArray, length, 2);
|
|
length += 2;
|
|
break;
|
|
case FieldType.Variable:
|
|
if (field.GetType().IsArray)
|
|
{
|
|
// Assume this is a byte array
|
|
fieldLength = ((byte[])field).Length;
|
|
|
|
if (fieldLength > 255)
|
|
{
|
|
Helpers.Log("Truncating variable (byte) field to 255 " +
|
|
"characters", Helpers.LogLevel.Warning);
|
|
|
|
fieldLength = 255;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Assume this is a string, add 1 for the null terminator
|
|
fieldLength = ((string)field).Length + 1;
|
|
|
|
if (fieldLength > 255)
|
|
{
|
|
Helpers.Log("Truncating variable (string) field to 255 " +
|
|
"characters", Helpers.LogLevel.Warning);
|
|
|
|
fieldLength = 255;
|
|
}
|
|
}
|
|
|
|
if (fieldMap.Count == 2)
|
|
{
|
|
Array.Copy(BitConverter.GetBytes(fieldLength), 0, byteArray,
|
|
length, 2);
|
|
length += 2;
|
|
}
|
|
else
|
|
{
|
|
if (fieldMap.Count != 1)
|
|
{
|
|
Helpers.Log("Variable field " + fieldMap.Name + " has a count of " +
|
|
+ fieldMap.Count + ", ignoring and assuming 1",
|
|
Helpers.LogLevel.Warning);
|
|
}
|
|
|
|
byteArray[length] = (byte)(fieldLength);
|
|
length++;
|
|
}
|
|
|
|
if (field.GetType().IsArray)
|
|
{
|
|
// Assume this is a byte array
|
|
Array.Copy((byte[])field, 0, byteArray, length, fieldLength);
|
|
}
|
|
else
|
|
{
|
|
// Assume this is a string, add 1 for the null terminator
|
|
byte[] stringBytes = System.Text.Encoding.UTF8.GetBytes((string)field);
|
|
Array.Copy(stringBytes, 0, byteArray, length, stringBytes.Length);
|
|
fieldLength = stringBytes.Length + 1;
|
|
}
|
|
|
|
length += fieldLength;
|
|
|
|
break;
|
|
case FieldType.Fixed:
|
|
Array.Copy((byte[])field, 0, byteArray, length, fieldMap.Count);
|
|
length += fieldMap.Count;
|
|
break;
|
|
default:
|
|
Helpers.Log("Unhandled field type " + fieldMap.Type + " during " +
|
|
"packet construction", Helpers.LogLevel.Error);
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
Helpers.Log("Data type " + field.GetType().ToString() + " for field " +
|
|
fieldMap.Name + " doesn't match expected type " + fieldMap.Type.ToString(),
|
|
Helpers.LogLevel.Error);
|
|
// This will fail for fixed or variable type packets, but it's a
|
|
// last ditch effort
|
|
length += (int)protocol.TypeSizes[fieldMap.Type];
|
|
}
|
|
#endregion AddField
|
|
}
|
|
else
|
|
{
|
|
// This field wasn't passed in, create an empty version
|
|
#region EmptyField
|
|
if (fieldMap.Type == FieldType.Variable)
|
|
{
|
|
// Just set the counter to zero and move on
|
|
if (fieldMap.Count == 2)
|
|
{
|
|
length += 2;
|
|
}
|
|
else
|
|
{
|
|
if (fieldMap.Count != 1)
|
|
{
|
|
Helpers.Log("Variable field " + fieldMap.Name + " has a count of " +
|
|
+ fieldMap.Count + ", ignoring and assuming 1",
|
|
Helpers.LogLevel.Warning);
|
|
}
|
|
|
|
length++;
|
|
}
|
|
}
|
|
else if (fieldMap.Type == FieldType.Fixed)
|
|
{
|
|
length += fieldMap.Count;
|
|
}
|
|
else
|
|
{
|
|
length += (int)protocol.TypeSizes[fieldMap.Type];
|
|
}
|
|
#endregion EmptyField
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Helpers.Log("Trying to build a " + packetMap.Name + " packet with too many " +
|
|
blockMap.Name + " blocks", Helpers.LogLevel.Warning);
|
|
}
|
|
}
|
|
}
|
|
#endregion BuildBlock
|
|
|
|
// If this is a fixed count block and it doesn't appear in the Hashtable passed in, create
|
|
// empty filler blocks
|
|
#region EmptyBlock
|
|
if (blockCount == 0 && blockMap.Count != -1)
|
|
{
|
|
for (int i = 0; i < blockMap.Count; ++i)
|
|
{
|
|
foreach (MapField field in blockMap.Fields)
|
|
{
|
|
if (field.Type == FieldType.Variable)
|
|
{
|
|
length++;
|
|
}
|
|
else
|
|
{
|
|
length += (int)protocol.TypeSizes[field.Type];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion EmptyBlock
|
|
}
|
|
|
|
return new Packet(byteArray, length, protocol);
|
|
}
|
|
|
|
public static Packet UseCircuitCode(ProtocolManager protocol, LLUUID agentID, LLUUID sessionID, uint code)
|
|
{
|
|
Packet packet = new Packet("UseCircuitCode", protocol, 44);
|
|
|
|
// Append the payload
|
|
Array.Copy(agentID.Data, 0, packet.Data, 8, 16);
|
|
Array.Copy(sessionID.Data, 0, packet.Data, 24, 16);
|
|
Array.Copy(BitConverter.GetBytes(code), 0, packet.Data, 40, 4);
|
|
|
|
// Set the packet flags
|
|
packet.Data[0] = Helpers.MSG_RELIABLE;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet CompleteAgentMovement(ProtocolManager protocol, LLUUID agentID, LLUUID sessionID, uint code)
|
|
{
|
|
Packet packet = new Packet("CompleteAgentMovement", protocol, 44);
|
|
|
|
// Append the payload
|
|
Array.Copy(agentID.Data, 0, packet.Data, 8, 16);
|
|
Array.Copy(sessionID.Data, 0, packet.Data, 24, 16);
|
|
Array.Copy(BitConverter.GetBytes(code), 0, packet.Data, 40, 4);
|
|
|
|
// Set the packet flags
|
|
packet.Data[0] = Helpers.MSG_RELIABLE;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet PacketAck(ProtocolManager protocol, ArrayList acks)
|
|
{
|
|
int pos = 9;
|
|
|
|
// Size of this packet is header (8) + block count (1) + 4 * number of blocks
|
|
Packet packet = new Packet("PacketAck", protocol, 9 + acks.Count * 4);
|
|
|
|
// Set the payload size
|
|
packet.Data[8] = (byte)acks.Count;
|
|
|
|
// Append the payload
|
|
foreach (uint ack in acks)
|
|
{
|
|
Array.Copy(BitConverter.GetBytes(ack), 0, packet.Data, pos, 4);
|
|
pos += 4;
|
|
}
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet CompletePingCheck(ProtocolManager protocol, byte pingID)
|
|
{
|
|
Packet packet = new Packet("CompletePingCheck", protocol, 6);
|
|
|
|
// Append the payload
|
|
packet.Data[5] = pingID;
|
|
|
|
// Set the packet flags
|
|
packet.Data[0] = Helpers.MSG_ZEROCODED;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet LogoutRequest(ProtocolManager protocol, LLUUID agentID, LLUUID sessionID)
|
|
{
|
|
Packet packet = new Packet("LogoutRequest", protocol, 40);
|
|
|
|
Array.Copy(agentID.Data, 0, packet.Data, 8, 16);
|
|
Array.Copy(sessionID.Data, 0, packet.Data, 24, 16);
|
|
|
|
packet.Data[0] = Helpers.MSG_RELIABLE + Helpers.MSG_ZEROCODED;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet TeleportLocationRequest(ProtocolManager protocol, U64 regionHandle, LLVector3 lookAt,
|
|
LLVector3 position, LLUUID agentID, LLUUID sessionID)
|
|
{
|
|
Packet packet = new Packet("LogoutRequest", protocol, 72);
|
|
|
|
Array.Copy(regionHandle.GetBytes(), 0, packet.Data, 8, 8);
|
|
Array.Copy(lookAt.GetBytes(), 0, packet.Data, 16, 12);
|
|
Array.Copy(position.GetBytes(), 0, packet.Data, 28, 12);
|
|
Array.Copy(agentID.Data, 0, packet.Data, 40, 16);
|
|
Array.Copy(sessionID.Data, 0, packet.Data, 56, 16);
|
|
|
|
packet.Data[0] = Helpers.MSG_RELIABLE;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet DirLandQuery(ProtocolManager protocol, bool reservedNewbie, bool forSale,
|
|
LLUUID queryID, bool auction, uint queryFlags, LLUUID agentID, LLUUID sessionID)
|
|
{
|
|
Packet packet = new Packet("DirLandQuery", protocol, 63);
|
|
|
|
packet.Data[8] = BitConverter.GetBytes(reservedNewbie)[0];
|
|
packet.Data[9] = BitConverter.GetBytes(forSale)[0];
|
|
Array.Copy(queryID.Data, 0, packet.Data, 10, 16);
|
|
packet.Data[26] = BitConverter.GetBytes(auction)[0];
|
|
Array.Copy(BitConverter.GetBytes(queryFlags), 0, packet.Data, 27, 4);
|
|
Array.Copy(agentID.Data, 0, packet.Data, 31, 16);
|
|
Array.Copy(sessionID.Data, 0, packet.Data, 47, 16);
|
|
|
|
// Set the packet flags
|
|
packet.Data[0] = Helpers.MSG_RELIABLE;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet DirFindQuery(ProtocolManager protocol, string queryText, LLUUID queryID,
|
|
LLUUID agentID, LLUUID sessionID)
|
|
{
|
|
int pos = 8;
|
|
Packet packet = new Packet("DirFindQuery", protocol, 66 + queryText.Length);
|
|
|
|
// QueryID
|
|
Array.Copy(queryID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// QueryFlags
|
|
packet.Data[pos] = 1;
|
|
pos += 8;
|
|
|
|
// Set the QueryText field and it's length byte
|
|
packet.Data[pos] = (byte)(queryText.Length + 1);
|
|
pos++;
|
|
System.Text.Encoding.UTF8.GetBytes(queryText, 0, queryText.Length, packet.Data, pos);
|
|
pos += queryText.Length + 1;
|
|
|
|
// Set the AgentID and SessionID
|
|
Array.Copy(agentID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
Array.Copy(sessionID.Data, 0, packet.Data, pos, 16);
|
|
|
|
// Set the packet flags
|
|
packet.Data[0] = Helpers.MSG_ZEROCODED + Helpers.MSG_RELIABLE;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet DirPeopleQuerySimple(ProtocolManager protocol, string name, bool online,
|
|
LLUUID agentID, LLUUID sessionID)
|
|
{
|
|
int pos = 12;
|
|
Packet packet = new Packet("DirPeopleQuery", protocol, 71 + name.Length);
|
|
|
|
// Set the name field and it's length byte
|
|
packet.Data[pos] = (byte)(name.Length + 1);
|
|
pos++;
|
|
System.Text.Encoding.UTF8.GetBytes(name, 0, name.Length, packet.Data, pos);
|
|
pos += name.Length + 1;
|
|
|
|
// Set the QueryID to 1
|
|
pos += 15;
|
|
packet.Data[pos] = 1;
|
|
pos ++;
|
|
|
|
// Set the online field
|
|
packet.Data[pos] = BitConverter.GetBytes(online)[0];
|
|
pos++;
|
|
|
|
// Skip some fields
|
|
pos += 8;
|
|
|
|
// Set the AgentID and SessionID
|
|
Array.Copy(agentID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
Array.Copy(sessionID.Data, 0, packet.Data, pos, 16);
|
|
|
|
// Set the packet flags
|
|
packet.Data[0] = Helpers.MSG_ZEROCODED + Helpers.MSG_RELIABLE;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet ParcelInfoRequest(ProtocolManager protocol, LLUUID parcelID, LLUUID agentID,
|
|
LLUUID sessionID)
|
|
{
|
|
Packet packet = new Packet("ParcelInfoRequest", protocol, 56);
|
|
|
|
Array.Copy(parcelID.Data, 0, packet.Data, 8, 16);
|
|
Array.Copy(agentID.Data, 0, packet.Data, 24, 16);
|
|
Array.Copy(sessionID.Data, 0, packet.Data, 40, 16);
|
|
|
|
packet.Data[0] = Helpers.MSG_RELIABLE + Helpers.MSG_ZEROCODED;
|
|
|
|
return packet;
|
|
}
|
|
|
|
/*public static Packet TeleportLocationRequest(ProtocolManager protocol, U64 regionHandle, LLVector3 lookAt,
|
|
LLVector3 position, LLUUID agentID, LLUUID sessionID)
|
|
{
|
|
Packet packet = new Packet("TeleportLocationRequest", protocol, 72);
|
|
|
|
Array.Copy(regionHandle.GetBytes(), 0, packet.Data, 8, 8);
|
|
Array.Copy(lookAt.GetBytes(), 0, packet.Data, 16, 12);
|
|
Array.Copy(position.GetBytes(), 0, packet.Data, 28, 12);
|
|
Array.Copy(agentID.Data, 0, packet.Data, 40, 16);
|
|
Array.Copy(sessionID.Data, 0, packet.Data, 56, 16);
|
|
|
|
// Set the packet flags
|
|
packet.Data[0] = Helpers.MSG_RELIABLE + Helpers.MSG_ZEROCODED;
|
|
|
|
return packet;
|
|
}*/
|
|
|
|
public static Packet FetchInventoryDescendents(ProtocolManager protocol, LLUUID ownerID, LLUUID folderID,
|
|
LLUUID agentID)
|
|
{
|
|
int packetLength = 8; // header
|
|
packetLength += 16; // OwnerID (UUID)
|
|
packetLength += 16; // FolderID (UUID)
|
|
packetLength += 4; // Sort Order (signed integer? - S32)
|
|
packetLength += 1; // FetchFolders (byte? - BOOL)
|
|
packetLength += 1; // FetchItmes (byte? - BOOL)
|
|
packetLength += 16; // AgentID (UUID)
|
|
|
|
|
|
Packet packet = new Packet("FetchInventoryDescendents", protocol, packetLength);
|
|
|
|
int pos = 8; // Leave room for header
|
|
|
|
// OwnerID
|
|
Array.Copy(ownerID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// FolderID
|
|
Array.Copy(folderID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// Sort Order (Any ideas on what the valid values are here?)
|
|
Array.Copy(BitConverter.GetBytes((int)1), 0, packet.Data, pos, 4);
|
|
pos += 4;
|
|
|
|
// FetchFolders
|
|
packet.Data[pos] = 1;
|
|
pos += 1;
|
|
|
|
// FetchItemss
|
|
packet.Data[pos] = 1;
|
|
pos += 1;
|
|
|
|
// AgentID
|
|
Array.Copy(agentID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// Set the packet flags
|
|
// packet.Data[0] = Helpers.MSG_ZEROCODED + Helpers.MSG_RELIABLE;
|
|
packet.Data[0] = Helpers.MSG_RELIABLE;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet RequestInventoryAsset(ProtocolManager protocol, LLUUID agentID, LLUUID queryUD,
|
|
LLUUID ownerID, LLUUID itemID)
|
|
{
|
|
int packetLength = 8; // header
|
|
packetLength += 16; // AgentID (UUID)
|
|
packetLength += 16; // QueryID (UUID)
|
|
packetLength += 16; // OwnerID (UUID)
|
|
packetLength += 16; // ItemID (UUID)
|
|
|
|
Packet packet = new Packet("RequestInventoryAsset", protocol, packetLength);
|
|
|
|
int pos = 8; // Leave room for header
|
|
|
|
// AgentID
|
|
Array.Copy(agentID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// QueryID
|
|
Array.Copy(queryUD.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// OwnerID
|
|
Array.Copy(ownerID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// ItemID
|
|
Array.Copy(itemID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// Set the packet flags
|
|
packet.Data[0] = Helpers.MSG_ZEROCODED + Helpers.MSG_RELIABLE;
|
|
|
|
return packet;
|
|
}
|
|
|
|
public static Packet InstantMessage(ProtocolManager protocol, LLUUID targetAgentID, LLUUID myAgentID,
|
|
uint parentEstateID, LLUUID regionID, LLVector3 position, byte offline, byte dialog, LLUUID id,
|
|
uint timestamp, string myAgentName, string message, string binaryBucket)
|
|
{
|
|
Hashtable blocks = new Hashtable();
|
|
Hashtable fields = new Hashtable();
|
|
|
|
fields["FromAgentID"] = myAgentID;
|
|
fields["ToAgentID"] = targetAgentID;
|
|
fields["ParentEstateID"] = parentEstateID;
|
|
fields["RegionID"] = regionID;
|
|
fields["Position"] = position;
|
|
fields["Offline"] = offline;
|
|
fields["Dialog"] = dialog;
|
|
fields["ID"] = id;
|
|
fields["Timestamp"] = timestamp;
|
|
fields["FromAgentName"] = myAgentName;
|
|
fields["Message"] = message;
|
|
fields["BinaryBucket"] = binaryBucket;
|
|
blocks[fields] = "MessageBlock";
|
|
|
|
Packet packet = PacketBuilder.BuildPacket("ImprovedInstantMessage", protocol, blocks);
|
|
return packet;
|
|
}
|
|
|
|
public static Packet Chat(ProtocolManager protocol, LLUUID myAgentID, LLUUID mySessionID, string message,
|
|
byte type, int channel, byte command, LLUUID commandID, float radius, LLVector3 position)
|
|
{
|
|
Hashtable blocks = new Hashtable();
|
|
Hashtable agentData = new Hashtable();
|
|
Hashtable chatData = new Hashtable();
|
|
Hashtable conversationData = new Hashtable();
|
|
|
|
// Agent Data Block
|
|
agentData["AgentID"] = myAgentID;
|
|
agentData["SessionID"] = mySessionID;
|
|
blocks[agentData] = "AgentData";
|
|
|
|
// Chat Data Block
|
|
chatData["Message"] = message;
|
|
chatData["Type"] = type;
|
|
chatData["Channel"] = channel;
|
|
blocks[chatData] = "ChatData";
|
|
|
|
// Conversation Data Block
|
|
conversationData["Command"] = command;
|
|
conversationData["CommandID"] = commandID;
|
|
conversationData["Radius"] = radius;
|
|
conversationData["Position"] = position;
|
|
blocks[conversationData] = "ConversationData";
|
|
|
|
Packet packet = PacketBuilder.BuildPacket("ChatFromViewer",protocol,blocks);
|
|
return packet;
|
|
}
|
|
|
|
public static Packet ObjectAddSimple(ProtocolManager protocol, PrimObject objectData, LLUUID senderID,
|
|
LLVector3 position, LLVector3 rayStart)
|
|
{
|
|
LLUUID woodTexture = new LLUUID("8955674724cb43ed920b47caed15465f");
|
|
LLUUID rayTargetID = new LLUUID("0f5d10f1f0a38634e893b70e00000000");
|
|
int length = 6 + 60 + 2 + objectData.NameValue.Length + 1 + 36 + 2 + 40 + 29;
|
|
Packet packet = new Packet("ObjectAdd", protocol, length);
|
|
int pos = 6;
|
|
|
|
// InventoryData appears 0 times
|
|
packet.Data[pos] = 0;
|
|
pos++;
|
|
|
|
// InventoryFile.Filename is of 1 length
|
|
packet.Data[pos] = 1;
|
|
pos++;
|
|
|
|
// InventoryFile.Filename is just a null terminator
|
|
packet.Data[pos] = 0;
|
|
pos++;
|
|
|
|
// U32 AddFlags = 2
|
|
uint addFlags = 2;
|
|
Array.Copy(BitConverter.GetBytes(addFlags), 0, packet.Data, pos, 4);
|
|
pos += 4;
|
|
|
|
packet.Data[pos] = (byte)objectData.PathTwistBegin;
|
|
pos++;
|
|
|
|
packet.Data[pos] = objectData.PathEnd;
|
|
pos++;
|
|
|
|
packet.Data[pos] = objectData.ProfileBegin;
|
|
pos++;
|
|
|
|
packet.Data[pos] = (byte)objectData.PathRadiusOffset;
|
|
pos++;
|
|
|
|
packet.Data[pos] = (byte)objectData.PathSkew;
|
|
pos++;
|
|
|
|
// SenderID
|
|
Array.Copy(senderID.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// RayStart
|
|
Array.Copy(rayStart.GetBytes(), 0, packet.Data, pos, 12);
|
|
pos += 12;
|
|
|
|
packet.Data[pos] = objectData.ProfileCurve;
|
|
pos++;
|
|
|
|
packet.Data[pos] = objectData.PathScaleX;
|
|
pos++;
|
|
|
|
packet.Data[pos] = objectData.PathScaleY;
|
|
pos++;
|
|
|
|
// Set GroupID to zero
|
|
pos += 16;
|
|
|
|
packet.Data[pos] = objectData.Material;
|
|
pos++;
|
|
|
|
if (objectData.NameValue.Length != 0)
|
|
{
|
|
// NameValue, begins with two bytes describing the size
|
|
Array.Copy(BitConverter.GetBytes((ushort)(objectData.NameValue.Length + 1)), 0, packet.Data, pos, 2);
|
|
pos += 2;
|
|
System.Text.Encoding.UTF8.GetBytes(objectData.NameValue, 0, objectData.NameValue.Length, packet.Data, pos);
|
|
// Jump an extra spot for the null terminator
|
|
pos += objectData.NameValue.Length + 1;
|
|
}
|
|
else
|
|
{
|
|
// Set the two size bytes to zero and increment
|
|
pos += 2;
|
|
}
|
|
|
|
packet.Data[pos] = objectData.PathShearX;
|
|
pos++;
|
|
|
|
packet.Data[pos] = objectData.PathShearY;
|
|
pos++;
|
|
|
|
packet.Data[pos] = (byte)objectData.PathTaperX;
|
|
pos++;
|
|
|
|
packet.Data[pos] = (byte)objectData.PathTaperY;
|
|
pos++;
|
|
|
|
// RayEndIsIntersection
|
|
packet.Data[pos] = 0;
|
|
pos++;
|
|
|
|
// RayEnd is the position to place the object
|
|
Array.Copy(position.GetBytes(), 0, packet.Data, pos, 12);
|
|
pos += 12;
|
|
|
|
packet.Data[pos] = objectData.ProfileEnd;
|
|
pos++;
|
|
|
|
packet.Data[pos] = objectData.PathBegin;
|
|
pos++;
|
|
|
|
// BypassRaycast is 0
|
|
packet.Data[pos] = 0;
|
|
pos++;
|
|
|
|
// PCode? set to 9
|
|
packet.Data[pos] = 9;
|
|
pos++;
|
|
|
|
packet.Data[pos] = objectData.PathCurve;
|
|
pos++;
|
|
|
|
// Scale
|
|
Array.Copy(objectData.Scale.GetBytes(), 0, packet.Data, pos, 12);
|
|
pos += 12;
|
|
|
|
// State? is 0
|
|
packet.Data[pos] = 0;
|
|
pos++;
|
|
|
|
packet.Data[pos] = (byte)objectData.PathTwist;
|
|
pos++;
|
|
|
|
// TextureEntry, starts with two bytes describing the size
|
|
Array.Copy(BitConverter.GetBytes((ushort)40), 0, packet.Data, pos, 2);
|
|
pos += 2;
|
|
Array.Copy(woodTexture.Data, 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
// Fill in the rest of TextureEntry
|
|
pos += 19;
|
|
packet.Data[pos] = 0xe0;
|
|
pos += 5;
|
|
|
|
packet.Data[pos] = objectData.ProfileHollow;
|
|
pos++;
|
|
|
|
packet.Data[pos] = objectData.PathRevolutions;
|
|
pos++;
|
|
|
|
// Rotation
|
|
Array.Copy(objectData.Rotation.GetBytes(), 0, packet.Data, pos, 16);
|
|
pos += 16;
|
|
|
|
// RayTargetID
|
|
Array.Copy(rayTargetID.Data, 0, packet.Data, pos, 12);
|
|
pos += 12;
|
|
|
|
// Set the packet flags
|
|
packet.Data[0] = /*Helpers.MSG_ZEROCODED +*/ Helpers.MSG_RELIABLE;
|
|
|
|
return packet;
|
|
}
|
|
}
|
|
}
|