LIBOMV-576 Start of Abstracting library into two separate libraries. For now this means: There will be a new dependency for OpenMetaverse.dll named OpenMetaverseCore.dll, the new will be required for OpenMetaverse to operate properly, the inverse is not true. OpenMetaverseCore will eventually contain all packet and message related code. * Need to create a singleton logger instance (or move the current logger to Core. * Currently only Packets, Helpers and some common types have been moved to Core. * Helpers will need to be split and non-core required helpers moved back to OpenMetaverse. * Lots more work to be done here, but these changes should not break anything (yet) git-svn-id: http://libopenmetaverse.googlecode.com/svn/libopenmetaverse/trunk@3021 52acb1d6-8a22-11de-b505-999d5b087335
707 lines
17 KiB
C#
707 lines
17 KiB
C#
/*
|
|
* Copyright (c) 2007-2008, openmetaverse.org
|
|
* 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 openmetaverse.org 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.Collections.Generic;
|
|
using System.IO;
|
|
using OpenMetaverse;
|
|
|
|
namespace OpenMetaverse
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public enum FieldType
|
|
{
|
|
/// <summary></summary>
|
|
U8,
|
|
/// <summary></summary>
|
|
U16,
|
|
/// <summary></summary>
|
|
U32,
|
|
/// <summary></summary>
|
|
U64,
|
|
/// <summary></summary>
|
|
S8,
|
|
/// <summary></summary>
|
|
S16,
|
|
/// <summary></summary>
|
|
S32,
|
|
/// <summary></summary>
|
|
F32,
|
|
/// <summary></summary>
|
|
F64,
|
|
/// <summary></summary>
|
|
UUID,
|
|
/// <summary></summary>
|
|
BOOL,
|
|
/// <summary></summary>
|
|
Vector3,
|
|
/// <summary></summary>
|
|
Vector3d,
|
|
/// <summary></summary>
|
|
Vector4,
|
|
/// <summary></summary>
|
|
Quaternion,
|
|
/// <summary></summary>
|
|
IPADDR,
|
|
/// <summary></summary>
|
|
IPPORT,
|
|
/// <summary></summary>
|
|
Variable,
|
|
/// <summary></summary>
|
|
Fixed,
|
|
/// <summary></summary>
|
|
Single,
|
|
/// <summary></summary>
|
|
Multiple
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class MapField : IComparable
|
|
{
|
|
/// <summary></summary>
|
|
public int KeywordPosition;
|
|
/// <summary></summary>
|
|
public string Name;
|
|
/// <summary></summary>
|
|
public FieldType Type;
|
|
/// <summary></summary>
|
|
public int Count;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="obj"></param>
|
|
/// <returns></returns>
|
|
public int CompareTo(object obj)
|
|
{
|
|
MapField temp = (MapField)obj;
|
|
|
|
if (this.KeywordPosition > temp.KeywordPosition)
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
if(temp.KeywordPosition == this.KeywordPosition)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class MapBlock : IComparable
|
|
{
|
|
/// <summary></summary>
|
|
public int KeywordPosition;
|
|
/// <summary></summary>
|
|
public string Name;
|
|
/// <summary></summary>
|
|
public int Count;
|
|
/// <summary></summary>
|
|
public List<MapField> Fields;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="obj"></param>
|
|
/// <returns></returns>
|
|
public int CompareTo(object obj)
|
|
{
|
|
MapBlock temp = (MapBlock)obj;
|
|
|
|
if (this.KeywordPosition > temp.KeywordPosition)
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
if(temp.KeywordPosition == this.KeywordPosition)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class MapPacket
|
|
{
|
|
/// <summary></summary>
|
|
public ushort ID;
|
|
/// <summary></summary>
|
|
public string Name;
|
|
/// <summary></summary>
|
|
public PacketFrequency Frequency;
|
|
/// <summary></summary>
|
|
public bool Trusted;
|
|
/// <summary></summary>
|
|
public bool Encoded;
|
|
/// <summary></summary>
|
|
public List<MapBlock> Blocks;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class ProtocolManager
|
|
{
|
|
/// <summary></summary>
|
|
public Dictionary<FieldType, int> TypeSizes;
|
|
/// <summary></summary>
|
|
public Dictionary<string, int> KeywordPositions;
|
|
/// <summary></summary>
|
|
public MapPacket[] LowMaps;
|
|
/// <summary></summary>
|
|
public MapPacket[] MediumMaps;
|
|
/// <summary></summary>
|
|
public MapPacket[] HighMaps;
|
|
|
|
private GridClient Client;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="mapFile"></param>
|
|
/// <param name="client"></param>
|
|
public ProtocolManager(string mapFile, GridClient client)
|
|
{
|
|
Client = client;
|
|
|
|
// Initialize the map arrays
|
|
LowMaps = new MapPacket[65536];
|
|
MediumMaps = new MapPacket[256];
|
|
HighMaps = new MapPacket[256];
|
|
|
|
// Build the type size hash table
|
|
TypeSizes = new Dictionary<FieldType,int>();
|
|
TypeSizes.Add(FieldType.U8, 1);
|
|
TypeSizes.Add(FieldType.U16, 2);
|
|
TypeSizes.Add(FieldType.U32, 4);
|
|
TypeSizes.Add(FieldType.U64, 8);
|
|
TypeSizes.Add(FieldType.S8, 1);
|
|
TypeSizes.Add(FieldType.S16, 2);
|
|
TypeSizes.Add(FieldType.S32, 4);
|
|
TypeSizes.Add(FieldType.F32, 4);
|
|
TypeSizes.Add(FieldType.F64, 8);
|
|
TypeSizes.Add(FieldType.UUID, 16);
|
|
TypeSizes.Add(FieldType.BOOL, 1);
|
|
TypeSizes.Add(FieldType.Vector3, 12);
|
|
TypeSizes.Add(FieldType.Vector3d, 24);
|
|
TypeSizes.Add(FieldType.Vector4, 16);
|
|
TypeSizes.Add(FieldType.Quaternion, 16);
|
|
TypeSizes.Add(FieldType.IPADDR, 4);
|
|
TypeSizes.Add(FieldType.IPPORT, 2);
|
|
TypeSizes.Add(FieldType.Variable, -1);
|
|
TypeSizes.Add(FieldType.Fixed, -2);
|
|
|
|
KeywordPositions = new Dictionary<string, int>();
|
|
LoadMapFile(mapFile);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="command"></param>
|
|
/// <returns></returns>
|
|
public MapPacket Command(string command)
|
|
{
|
|
foreach (MapPacket map in HighMaps)
|
|
{
|
|
if (map != null)
|
|
{
|
|
if (command == map.Name)
|
|
{
|
|
return map;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (MapPacket map in MediumMaps)
|
|
{
|
|
if (map != null)
|
|
{
|
|
if (command == map.Name)
|
|
{
|
|
return map;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (MapPacket map in LowMaps)
|
|
{
|
|
if (map != null)
|
|
{
|
|
if (command == map.Name)
|
|
{
|
|
return map;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Exception("Cannot find map for command \"" + command + "\"");
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
/// <returns></returns>
|
|
public MapPacket Command(byte[] data)
|
|
{
|
|
ushort command;
|
|
|
|
if (data.Length < 5)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (data[4] == 0xFF)
|
|
{
|
|
if ((byte)data[5] == 0xFF)
|
|
{
|
|
// Low frequency
|
|
command = (ushort)(data[6] * 256 + data[7]);
|
|
return Command(command, PacketFrequency.Low);
|
|
}
|
|
else
|
|
{
|
|
// Medium frequency
|
|
command = (ushort)data[5];
|
|
return Command(command, PacketFrequency.Medium);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// High frequency
|
|
command = (ushort)data[4];
|
|
return Command(command, PacketFrequency.High);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="command"></param>
|
|
/// <param name="frequency"></param>
|
|
/// <returns></returns>
|
|
public MapPacket Command(ushort command, PacketFrequency frequency)
|
|
{
|
|
switch (frequency)
|
|
{
|
|
case PacketFrequency.High:
|
|
return HighMaps[command];
|
|
case PacketFrequency.Medium:
|
|
return MediumMaps[command];
|
|
case PacketFrequency.Low:
|
|
return LowMaps[command];
|
|
}
|
|
|
|
throw new Exception("Cannot find map for command \"" + command + "\" with frequency \"" + frequency + "\"");
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public void PrintMap()
|
|
{
|
|
PrintOneMap(LowMaps, "Low ");
|
|
PrintOneMap(MediumMaps, "Medium");
|
|
PrintOneMap(HighMaps, "High ");
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="map"></param>
|
|
/// <param name="frequency"></param>
|
|
private void PrintOneMap(MapPacket[] map, string frequency) {
|
|
int i;
|
|
|
|
for (i = 0; i < map.Length; ++i)
|
|
{
|
|
if (map[i] != null)
|
|
{
|
|
Console.WriteLine("{0} {1,5} - {2} - {3} - {4}", frequency, i, map[i].Name,
|
|
map[i].Trusted ? "Trusted" : "Untrusted",
|
|
map[i].Encoded ? "Unencoded" : "Zerocoded");
|
|
|
|
foreach (MapBlock block in map[i].Blocks)
|
|
{
|
|
if (block.Count == -1)
|
|
{
|
|
Console.WriteLine("\t{0,4} {1} (Variable)", block.KeywordPosition, block.Name);
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("\t{0,4} {1} ({2})", block.KeywordPosition, block.Name, block.Count);
|
|
}
|
|
|
|
foreach (MapField field in block.Fields)
|
|
{
|
|
Console.WriteLine("\t\t{0,4} {1} ({2} / {3})", field.KeywordPosition, field.Name,
|
|
field.Type, field.Count);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="mapFile"></param>
|
|
/// <param name="outputFile"></param>
|
|
public static void DecodeMapFile(string mapFile, string outputFile)
|
|
{
|
|
byte magicKey = 0;
|
|
byte[] buffer = new byte[2048];
|
|
int nread;
|
|
BinaryReader map;
|
|
BinaryWriter output;
|
|
|
|
try
|
|
{
|
|
map = new BinaryReader(new FileStream(mapFile, FileMode.Open));
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
throw new Exception("Map file error", e);
|
|
}
|
|
|
|
try
|
|
{
|
|
output = new BinaryWriter(new FileStream(outputFile, FileMode.CreateNew));
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
throw new Exception("Map file error", e);
|
|
}
|
|
|
|
while ((nread = map.Read(buffer, 0, 2048)) != 0)
|
|
{
|
|
for (int i = 0; i < nread; ++i)
|
|
{
|
|
buffer[i] ^= magicKey;
|
|
magicKey += 43;
|
|
}
|
|
|
|
output.Write(buffer, 0, nread);
|
|
}
|
|
|
|
map.Close();
|
|
output.Close();
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="mapFile"></param>
|
|
private void LoadMapFile(string mapFile)
|
|
{
|
|
FileStream map;
|
|
ushort low = 1;
|
|
ushort medium = 1;
|
|
ushort high = 1;
|
|
|
|
// Load the protocol map file
|
|
try
|
|
{
|
|
map = new FileStream(mapFile, FileMode.Open, FileAccess.Read);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
throw new Exception("Map file loading error", e);
|
|
}
|
|
|
|
try
|
|
{
|
|
StreamReader r = new StreamReader(map);
|
|
r.BaseStream.Seek(0, SeekOrigin.Begin);
|
|
string newline;
|
|
string trimmedline;
|
|
bool inPacket = false;
|
|
bool inBlock = false;
|
|
MapPacket currentPacket = null;
|
|
MapBlock currentBlock = null;
|
|
char[] trimArray = new char[] {' ', '\t'};
|
|
|
|
// While not at the end of the file
|
|
while (r.Peek() > -1)
|
|
{
|
|
#region ParseMap
|
|
|
|
newline = r.ReadLine();
|
|
trimmedline = System.Text.RegularExpressions.Regex.Replace(newline, @"\s+", " ");
|
|
trimmedline = trimmedline.Trim(trimArray);
|
|
|
|
if (!inPacket)
|
|
{
|
|
// Outside of all packet blocks
|
|
|
|
if (trimmedline == "{")
|
|
{
|
|
inPacket = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Inside of a packet block
|
|
|
|
if (!inBlock)
|
|
{
|
|
// Inside a packet block, outside of the blocks
|
|
|
|
if (trimmedline == "{")
|
|
{
|
|
inBlock = true;
|
|
}
|
|
else if (trimmedline == "}")
|
|
{
|
|
// Reached the end of the packet
|
|
currentPacket.Blocks.Sort();
|
|
inPacket = false;
|
|
}
|
|
else
|
|
{
|
|
// The packet header
|
|
#region ParsePacketHeader
|
|
|
|
// Splice the string in to tokens
|
|
string[] tokens = trimmedline.Split(new char[] {' ', '\t'});
|
|
|
|
if (tokens.Length > 3)
|
|
{
|
|
//Hash packet name to insure correct keyword ordering
|
|
KeywordPosition(tokens[0]);
|
|
|
|
if (tokens[1] == "Fixed")
|
|
{
|
|
// Remove the leading "0x"
|
|
if (tokens[2].Substring(0, 2) == "0x")
|
|
{
|
|
tokens[2] = tokens[2].Substring(2, tokens[2].Length - 2);
|
|
}
|
|
|
|
uint fixedID = UInt32.Parse(tokens[2], System.Globalization.NumberStyles.HexNumber);
|
|
// Truncate the id to a short
|
|
fixedID ^= 0xFFFF0000;
|
|
LowMaps[fixedID] = new MapPacket();
|
|
LowMaps[fixedID].ID = (ushort)fixedID;
|
|
LowMaps[fixedID].Frequency = PacketFrequency.Low;
|
|
LowMaps[fixedID].Name = tokens[0];
|
|
LowMaps[fixedID].Trusted = (tokens[3] == "Trusted");
|
|
LowMaps[fixedID].Encoded = (tokens[4] == "Zerocoded");
|
|
LowMaps[fixedID].Blocks = new List<MapBlock>();
|
|
|
|
currentPacket = LowMaps[fixedID];
|
|
}
|
|
else if (tokens[1] == "Low")
|
|
{
|
|
LowMaps[low] = new MapPacket();
|
|
LowMaps[low].ID = low;
|
|
LowMaps[low].Frequency = PacketFrequency.Low;
|
|
LowMaps[low].Name = tokens[0];
|
|
LowMaps[low].Trusted = (tokens[2] == "Trusted");
|
|
LowMaps[low].Encoded = (tokens[3] == "Zerocoded");
|
|
LowMaps[low].Blocks = new List<MapBlock>();
|
|
|
|
currentPacket = LowMaps[low];
|
|
|
|
low++;
|
|
}
|
|
else if (tokens[1] == "Medium")
|
|
{
|
|
MediumMaps[medium] = new MapPacket();
|
|
MediumMaps[medium].ID = medium;
|
|
MediumMaps[medium].Frequency = PacketFrequency.Medium;
|
|
MediumMaps[medium].Name = tokens[0];
|
|
MediumMaps[medium].Trusted = (tokens[2] == "Trusted");
|
|
MediumMaps[medium].Encoded = (tokens[3] == "Zerocoded");
|
|
MediumMaps[medium].Blocks = new List<MapBlock>();
|
|
|
|
currentPacket = MediumMaps[medium];
|
|
|
|
medium++;
|
|
}
|
|
else if (tokens[1] == "High")
|
|
{
|
|
HighMaps[high] = new MapPacket();
|
|
HighMaps[high].ID = high;
|
|
HighMaps[high].Frequency = PacketFrequency.High;
|
|
HighMaps[high].Name = tokens[0];
|
|
HighMaps[high].Trusted = (tokens[2] == "Trusted");
|
|
HighMaps[high].Encoded = (tokens[3] == "Zerocoded");
|
|
HighMaps[high].Blocks = new List<MapBlock>();
|
|
|
|
currentPacket = HighMaps[high];
|
|
|
|
high++;
|
|
}
|
|
else
|
|
{
|
|
Logger.Log("Unknown packet frequency", Helpers.LogLevel.Error, Client);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (trimmedline.Length > 0 && trimmedline.Substring(0, 1) == "{")
|
|
{
|
|
// A field
|
|
#region ParseField
|
|
|
|
MapField field = new MapField();
|
|
|
|
// Splice the string in to tokens
|
|
string[] tokens = trimmedline.Split(new char[] {' ', '\t'});
|
|
|
|
field.Name = tokens[1];
|
|
field.KeywordPosition = KeywordPosition(field.Name);
|
|
field.Type = (FieldType)Enum.Parse(typeof(FieldType), tokens[2], true);
|
|
|
|
if (tokens[3] != "}")
|
|
{
|
|
field.Count = Int32.Parse(tokens[3]);
|
|
}
|
|
else
|
|
{
|
|
field.Count = 1;
|
|
}
|
|
|
|
// Save this field to the current block
|
|
currentBlock.Fields.Add(field);
|
|
|
|
#endregion
|
|
}
|
|
else if (trimmedline == "}")
|
|
{
|
|
currentBlock.Fields.Sort();
|
|
inBlock = false;
|
|
}
|
|
else if (trimmedline.Length != 0 && trimmedline.Substring(0, 2) != "//")
|
|
{
|
|
// The block header
|
|
#region ParseBlockHeader
|
|
|
|
currentBlock = new MapBlock();
|
|
|
|
// Splice the string in to tokens
|
|
string[] tokens = trimmedline.Split(new char[] {' ', '\t'});
|
|
|
|
currentBlock.Name = tokens[0];
|
|
currentBlock.KeywordPosition = KeywordPosition(currentBlock.Name);
|
|
currentBlock.Fields = new List<MapField>();
|
|
currentPacket.Blocks.Add(currentBlock);
|
|
|
|
if (tokens[1] == "Single")
|
|
{
|
|
currentBlock.Count = 1;
|
|
}
|
|
else if (tokens[1] == "Multiple")
|
|
{
|
|
currentBlock.Count = Int32.Parse(tokens[2]);
|
|
}
|
|
else if (tokens[1] == "Variable")
|
|
{
|
|
currentBlock.Count = -1;
|
|
}
|
|
else
|
|
{
|
|
Logger.Log("Unknown block frequency", Helpers.LogLevel.Error, Client);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
r.Close();
|
|
map.Close();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new Exception("Map file parsing error", e); ;
|
|
}
|
|
}
|
|
|
|
private int KeywordPosition(string keyword)
|
|
{
|
|
if (KeywordPositions.ContainsKey(keyword))
|
|
{
|
|
return KeywordPositions[keyword];
|
|
}
|
|
|
|
int hash = 0;
|
|
for (int i = 1; i < keyword.Length; i++)
|
|
{
|
|
hash = (hash + (int)(keyword[i])) * 2;
|
|
}
|
|
hash *= 2;
|
|
hash &= 0x1FFF;
|
|
|
|
int startHash = hash;
|
|
|
|
while (KeywordPositions.ContainsValue(hash))
|
|
{
|
|
hash++;
|
|
hash &= 0x1FFF;
|
|
if (hash == startHash)
|
|
{
|
|
//Give up looking, went through all values and they were all taken.
|
|
throw new Exception("All hash values are taken. Failed to add keyword: " + keyword);
|
|
}
|
|
}
|
|
|
|
KeywordPositions[keyword] = hash;
|
|
return hash;
|
|
}
|
|
}
|
|
}
|