git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1624 52acb1d6-8a22-11de-b505-999d5b087335
1343 lines
50 KiB
C#
1343 lines
50 KiB
C#
/*
|
|
* Copyright (c) 2006-2007, 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.Collections.Generic;
|
|
using System.Text;
|
|
using libsecondlife.Packets;
|
|
|
|
namespace libsecondlife
|
|
{
|
|
/// <summary>
|
|
/// Static helper functions and global variables
|
|
/// </summary>
|
|
public class Helpers
|
|
{
|
|
/// <summary>
|
|
/// Operating system enumeration
|
|
/// </summary>
|
|
public enum Platform
|
|
{
|
|
/// <summary>Unknown</summary>
|
|
Unknown,
|
|
/// <summary>Microsoft Windows</summary>
|
|
Windows,
|
|
/// <summary>Microsoft Windows CE</summary>
|
|
WindowsCE,
|
|
/// <summary>Linux</summary>
|
|
Linux,
|
|
/// <summary>Apple OSX</summary>
|
|
OSX
|
|
}
|
|
|
|
/// <summary>This header flag signals that ACKs are appended to the packet</summary>
|
|
public const byte MSG_APPENDED_ACKS = 0x10;
|
|
/// <summary>This header flag signals that this packet has been sent before</summary>
|
|
public const byte MSG_RESENT = 0x20;
|
|
/// <summary>This header flags signals that an ACK is expected for this packet</summary>
|
|
public const byte MSG_RELIABLE = 0x40;
|
|
/// <summary>This header flag signals that the message is compressed using zerocoding</summary>
|
|
public const byte MSG_ZEROCODED = 0x80;
|
|
/// <summary>Used for converting degrees to radians</summary>
|
|
public const double DEG_TO_RAD = Math.PI / 180.0d;
|
|
/// <summary>Used for converting radians to degrees</summary>
|
|
public const double RAD_TO_DEG = 180.0d / Math.PI;
|
|
/// <summary></summary>
|
|
public const float PI = (float)Math.PI;
|
|
/// <summary></summary>
|
|
public const float TWO_PI = (float)(Math.PI * 2d);
|
|
|
|
#if PocketPC
|
|
public static string NewLine = "\r\n";
|
|
#else
|
|
public static string NewLine = Environment.NewLine;
|
|
#endif
|
|
|
|
/// <summary>UNIX epoch in DateTime format</summary>
|
|
public static DateTime Epoch = new DateTime(1970, 1, 1);
|
|
|
|
/// <summary>
|
|
/// Passed to SecondLife.Log() to identify the severity of a log entry
|
|
/// </summary>
|
|
public enum LogLevel
|
|
{
|
|
/// <summary>Non-noisy useful information, may be helpful in
|
|
/// debugging a problem</summary>
|
|
Info,
|
|
/// <summary>A non-critical error occurred. A warning will not
|
|
/// prevent the rest of libsecondlife from operating as usual,
|
|
/// although it may be indicative of an underlying issue</summary>
|
|
Warning,
|
|
/// <summary>A critical error has occurred. Generally this will
|
|
/// be followed by the network layer shutting down, although the
|
|
/// stability of libsecondlife after an error is uncertain</summary>
|
|
Error,
|
|
/// <summary>Used for internal testing, this logging level can
|
|
/// generate very noisy (long and/or repetitive) messages. Don't
|
|
/// pass this to the Log() function, use DebugLog() instead.
|
|
/// </summary>
|
|
Debug
|
|
};
|
|
|
|
/// <summary>Provide a single instance of the MD5 class to avoid making
|
|
/// duplicate copies</summary>
|
|
public static System.Security.Cryptography.MD5 MD5Builder =
|
|
new System.Security.Cryptography.MD5CryptoServiceProvider();
|
|
|
|
/// <summary>Provide a single instance of the CultureInfo class to
|
|
/// help parsing in situations where Second Life assumes an en-us
|
|
/// culture</summary>
|
|
public static readonly System.Globalization.CultureInfo EnUsCulture =
|
|
new System.Globalization.CultureInfo("en-us");
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="offset"></param>
|
|
/// <returns></returns>
|
|
public static short TEOffsetShort(float offset)
|
|
{
|
|
offset = Clamp(offset, -1.0f, 1.0f);
|
|
offset *= 32767.0f;
|
|
return (short)Math.Round(offset);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="bytes"></param>
|
|
/// <param name="pos"></param>
|
|
/// <returns></returns>
|
|
public static float TEOffsetFloat(byte[] bytes, int pos)
|
|
{
|
|
float offset = (float)BitConverter.ToInt16(bytes, pos);
|
|
return offset / 32767.0f;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="rotation"></param>
|
|
/// <returns></returns>
|
|
public static short TERotationShort(float rotation)
|
|
{
|
|
const double TWO_PI = Math.PI * 2.0d;
|
|
double remainder = Math.IEEERemainder(rotation, TWO_PI);
|
|
return (short)Math.Round((remainder / TWO_PI) * 32767.0d);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="bytes"></param>
|
|
/// <param name="pos"></param>
|
|
/// <returns></returns>
|
|
public static float TERotationFloat(byte[] bytes, int pos)
|
|
{
|
|
const float TWO_PI = (float)(Math.PI * 2.0d);
|
|
return (float)((bytes[pos] | (bytes[pos + 1] << 8)) / 32767.0f) * TWO_PI;
|
|
}
|
|
|
|
public static byte TEGlowByte(float glow)
|
|
{
|
|
return (byte)(glow * 255.0f);
|
|
}
|
|
|
|
public static float TEGlowFloat(byte[] bytes, int pos)
|
|
{
|
|
return (float)bytes[pos] / 255.0f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts an unsigned integer to a hexadecimal string
|
|
/// </summary>
|
|
/// <param name="i">An unsigned integer to convert to a string</param>
|
|
/// <returns>A hexadecimal string 10 characters long</returns>
|
|
/// <example>0x7fffffff</example>
|
|
public static string UIntToHexString(uint i)
|
|
{
|
|
return string.Format("{0:x8}", i);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Packs to 32-bit unsigned integers in to a 64-bit unsigned integer
|
|
/// </summary>
|
|
/// <param name="a">The left-hand (or X) value</param>
|
|
/// <param name="b">The right-hand (or Y) value</param>
|
|
/// <returns>A 64-bit integer containing the two 32-bit input values</returns>
|
|
public static ulong UIntsToLong(uint a, uint b)
|
|
{
|
|
return ((ulong)a << 32) | (ulong)b;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given an X/Y location in absolute (grid-relative) terms, a region
|
|
/// handle is returned along with the local X/Y location in that region
|
|
/// </summary>
|
|
/// <param name="globalX">The absolute X location, a number such as
|
|
/// 255360.35</param>
|
|
/// <param name="globalY">The absolute Y location, a number such as
|
|
/// 255360.35</param>
|
|
/// <param name="localX">The sim-local X position of the global X
|
|
/// position, a value from 0.0 to 256.0</param>
|
|
/// <param name="localY">The sim-local Y position of the global Y
|
|
/// position, a value from 0.0 to 256.0</param>
|
|
/// <returns>A 64-bit region handle that can be used to teleport to</returns>
|
|
public static ulong GlobalPosToRegionHandle(float globalX, float globalY, out float localX, out float localY)
|
|
{
|
|
uint x = ((uint)globalX / 256) * 256;
|
|
uint y = ((uint)globalY / 256) * 256;
|
|
localX = globalX - (float)x;
|
|
localY = globalY - (float)y;
|
|
return UIntsToLong(x, y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unpacks two 32-bit unsigned integers from a 64-bit unsigned integer
|
|
/// </summary>
|
|
/// <param name="a">The 64-bit input integer</param>
|
|
/// <param name="b">The left-hand (or X) output value</param>
|
|
/// <param name="c">The right-hand (or Y) output value</param>
|
|
public static void LongToUInts(ulong a, out uint b, out uint c)
|
|
{
|
|
b = (uint)(a >> 32);
|
|
c = (uint)(a & 0x00000000FFFFFFFF);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert an integer to a byte array in little endian format
|
|
/// </summary>
|
|
/// <param name="x">The integer to convert</param>
|
|
/// <returns>A four byte little endian array</returns>
|
|
public static byte[] IntToBytes(int x)
|
|
{
|
|
byte[] bytes = new byte[4];
|
|
|
|
bytes[0]= (byte)(x % 256);
|
|
bytes[1] = (byte)((x >> 8) % 256);
|
|
bytes[2] = (byte)((x >> 16) % 256);
|
|
bytes[3] = (byte)((x >> 24) % 256);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the first two bytes starting at the given position in
|
|
/// little endian ordering to an unsigned short
|
|
/// </summary>
|
|
/// <param name="bytes">Byte array containing the ushort</param>
|
|
/// <param name="pos">Position to start reading the ushort from</param>
|
|
/// <returns>An unsigned short, will be zero if a ushort can't be read
|
|
/// at the given position</returns>
|
|
public static ushort BytesToUInt16(byte[] bytes, int pos)
|
|
{
|
|
if (bytes.Length <= pos + 1) return 0;
|
|
return (ushort)(bytes[pos] + (bytes[pos + 1] << 8));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the first four bytes starting at the given position in
|
|
/// little endian ordering to an unsigned integer
|
|
/// </summary>
|
|
/// <param name="bytes">Byte array containing the uint</param>
|
|
/// <param name="pos">Position to start reading the uint from</param>
|
|
/// <returns>An unsigned integer, will be zero if a uint can't be read
|
|
/// at the given position</returns>
|
|
public static uint BytesToUInt(byte[] bytes, int pos)
|
|
{
|
|
if (bytes.Length <= pos + 4) return 0;
|
|
return (uint)(bytes[pos + 3] + (bytes[pos + 2] << 8) + (bytes[pos + 1] << 16) + (bytes[pos] << 24));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the first four bytes of the given array in little endian
|
|
/// ordering to an unsigned integer
|
|
/// </summary>
|
|
/// <param name="bytes">An array four bytes or longer</param>
|
|
/// <returns>An unsigned integer, will be zero if the array contains
|
|
/// less than four bytes</returns>
|
|
public static uint BytesToUInt(byte[] bytes)
|
|
{
|
|
return BytesToUInt(bytes, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the first four bytes starting at the given position in
|
|
/// big endian ordering to an unsigned integer
|
|
/// </summary>
|
|
/// <param name="bytes">Byte array containing the uint</param>
|
|
/// <param name="pos">Position to start reading the uint from</param>
|
|
/// <returns>An unsigned integer, will be zero if a uint can't be read
|
|
/// at the given position</returns>
|
|
public static uint BytesToUIntBig(byte[] bytes, int pos)
|
|
{
|
|
if (bytes.Length <= pos + 4) return 0;
|
|
return (uint)(bytes[pos] + (bytes[pos + 1] << 8) + (bytes[pos + 2] << 16) + (bytes[pos + 3] << 24));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the first four bytes of the given array in big endian
|
|
/// ordering to an unsigned integer
|
|
/// </summary>
|
|
/// <param name="bytes">An array four bytes or longer</param>
|
|
/// <returns>An unsigned integer, will be zero if the array contains
|
|
/// less than four bytes</returns>
|
|
public static uint BytesToUIntBig(byte[] bytes)
|
|
{
|
|
if (bytes.Length < 4) return 0;
|
|
return (uint)(bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the first eight bytes of the given array in little endian
|
|
/// ordering to an unsigned 64-bit integer
|
|
/// </summary>
|
|
/// <param name="bytes">An array eight bytes or longer</param>
|
|
/// <returns>An unsigned 64-bit integer, will be zero if the array
|
|
/// contains less than eight bytes</returns>
|
|
public static ulong BytesToUInt64(byte[] bytes)
|
|
{
|
|
if (bytes.Length < 8) return 0;
|
|
return (ulong)
|
|
((ulong)bytes[7] +
|
|
((ulong)bytes[6] << 8) +
|
|
((ulong)bytes[5] << 16) +
|
|
((ulong)bytes[4] << 24) +
|
|
((ulong)bytes[3] << 32) +
|
|
((ulong)bytes[2] << 40) +
|
|
((ulong)bytes[1] << 48) +
|
|
((ulong)bytes[0] << 56));
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="bytes"></param>
|
|
/// <param name="pos"></param>
|
|
/// <returns></returns>
|
|
public static float BytesToFloat(byte[] bytes, int pos)
|
|
{
|
|
if (!BitConverter.IsLittleEndian) Array.Reverse(bytes, pos, 4);
|
|
return BitConverter.ToSingle(bytes, pos);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a floating point value to four bytes in little endian
|
|
/// ordering
|
|
/// </summary>
|
|
/// <param name="value">A floating point value</param>
|
|
/// <returns>A four byte array containing the value in little endian
|
|
/// ordering</returns>
|
|
public static byte[] FloatToBytes(float value)
|
|
{
|
|
byte[] bytes = BitConverter.GetBytes(value);
|
|
if (!BitConverter.IsLittleEndian)
|
|
Array.Reverse(bytes);
|
|
return bytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a floating point number to a terse string format used for
|
|
/// transmitting numbers in wearable asset files
|
|
/// </summary>
|
|
/// <param name="val">Floating point number to convert to a string</param>
|
|
/// <returns>A terse string representation of the input number</returns>
|
|
public static string FloatToTerseString(float val)
|
|
{
|
|
string s = string.Format("{0:.00}", val);
|
|
|
|
// Trim trailing zeroes
|
|
while (s[s.Length - 1] == '0')
|
|
s = s.Remove(s.Length - 1, 1);
|
|
|
|
// Remove superfluous decimal places after the trim
|
|
if (s[s.Length - 1] == '.')
|
|
s = s.Remove(s.Length - 1, 1);
|
|
// Remove leading zeroes after a negative sign
|
|
else if (s[0] == '-' && s[1] == '0')
|
|
s = s.Remove(1, 1);
|
|
// Remove leading zeroes in positive numbers
|
|
else if (s[0] == '0')
|
|
s = s.Remove(0, 1);
|
|
|
|
return s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a float value to a byte given a minimum and maximum range
|
|
/// </summary>
|
|
/// <param name="val">Value to convert to a byte</param>
|
|
/// <param name="lower">Minimum value range</param>
|
|
/// <param name="upper">Maximum value range</param>
|
|
/// <returns>A single byte representing the original float value</returns>
|
|
public static byte FloatToByte(float val, float lower, float upper)
|
|
{
|
|
val = Clamp(val, lower, upper);
|
|
// Normalize the value
|
|
val -= lower;
|
|
val /= (upper - lower);
|
|
|
|
return (byte)Math.Floor(val * (float)byte.MaxValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a byte to a float value given a minimum and maximum range
|
|
/// </summary>
|
|
/// <param name="bytes">Byte array to get the byte from</param>
|
|
/// <param name="pos">Position in the byte array the desired byte is at</param>
|
|
/// <param name="lower">Minimum value range</param>
|
|
/// <param name="upper">Maximum value range</param>
|
|
/// <returns>A float value inclusively between lower and upper</returns>
|
|
public static float ByteToFloat(byte[] bytes, int pos, float lower, float upper)
|
|
{
|
|
if (bytes.Length <= pos) return 0;
|
|
return ByteToFloat(bytes[pos], lower, upper);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a byte to a float value given a minimum and maximum range
|
|
/// </summary>
|
|
/// <param name="val">Byte to convert to a float value</param>
|
|
/// <param name="lower">Minimum value range</param>
|
|
/// <param name="upper">Maximum value range</param>
|
|
/// <returns>A float value inclusively between lower and upper</returns>
|
|
public static float ByteToFloat(byte val, float lower, float upper)
|
|
{
|
|
const float ONE_OVER_BYTEMAX = 1.0f / (float)byte.MaxValue;
|
|
|
|
float fval = (float)val * ONE_OVER_BYTEMAX;
|
|
float delta = (upper - lower);
|
|
fval *= delta;
|
|
fval += lower;
|
|
|
|
// Test for values very close to zero
|
|
float error = delta * ONE_OVER_BYTEMAX;
|
|
if (Math.Abs(fval) < error)
|
|
fval = 0.0f;
|
|
|
|
return fval;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="bytes"></param>
|
|
/// <param name="pos"></param>
|
|
/// <param name="lower"></param>
|
|
/// <param name="upper"></param>
|
|
/// <returns></returns>
|
|
public static float UInt16ToFloat(byte[] bytes, int pos, float lower, float upper)
|
|
{
|
|
ushort val = BytesToUInt16(bytes, pos);
|
|
return UInt16ToFloat(val, lower, upper);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="val"></param>
|
|
/// <param name="lower"></param>
|
|
/// <param name="upper"></param>
|
|
/// <returns></returns>
|
|
public static float UInt16ToFloat(ushort val, float lower, float upper)
|
|
{
|
|
const float ONE_OVER_U16_MAX = 1.0f / 65535.0f;
|
|
|
|
float fval = (float)val * ONE_OVER_U16_MAX;
|
|
float delta = upper - lower;
|
|
fval *= delta;
|
|
fval += lower;
|
|
|
|
// Make sure zeroes come through as zero
|
|
float maxError = delta * ONE_OVER_U16_MAX;
|
|
if (Math.Abs(fval) < maxError)
|
|
fval = 0.0f;
|
|
|
|
return fval;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert an IP address object to an unsigned 32-bit integer
|
|
/// </summary>
|
|
/// <param name="address">IP address to convert</param>
|
|
/// <returns>32-bit unsigned integer holding the IP address bits</returns>
|
|
public static uint IPToUInt(System.Net.IPAddress address)
|
|
{
|
|
byte[] bytes = address.GetAddressBytes();
|
|
return (uint)((bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clamp a given value between a range
|
|
/// </summary>
|
|
/// <param name="val">Value to clamp</param>
|
|
/// <param name="lower">Minimum allowable value</param>
|
|
/// <param name="upper">Maximum allowable value</param>
|
|
/// <returns>A value inclusively between lower and upper</returns>
|
|
public static float Clamp(float val, float lower, float upper)
|
|
{
|
|
return Math.Min(Math.Max(val, lower), upper);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a variable length UTF8 byte array to a string
|
|
/// </summary>
|
|
/// <param name="bytes">The UTF8 encoded byte array to convert</param>
|
|
/// <returns>The decoded string</returns>
|
|
public static string FieldToUTF8String(byte[] bytes)
|
|
{
|
|
if (bytes.Length > 0 && bytes[bytes.Length - 1] == 0x00)
|
|
return UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length - 1);
|
|
else
|
|
return UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a variable length field (byte array) to a string
|
|
/// </summary>
|
|
/// <remarks>If the byte array has unprintable characters in it, a
|
|
/// hex dump will be written instead</remarks>
|
|
/// <param name="output">The StringBuilder object to write to</param>
|
|
/// <param name="bytes">The byte array to convert to a string</param>
|
|
internal static void FieldToString(StringBuilder output, byte[] bytes)
|
|
{
|
|
FieldToString(output, bytes, String.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a variable length field (byte array) to a string, with a
|
|
/// field name prepended to each line of the output
|
|
/// </summary>
|
|
/// <remarks>If the byte array has unprintable characters in it, a
|
|
/// hex dump will be written instead</remarks>
|
|
/// <param name="output">The StringBuilder object to write to</param>
|
|
/// <param name="bytes">The byte array to convert to a string</param>
|
|
/// <param name="fieldName">A field name to prepend to each line of output</param>
|
|
internal static void FieldToString(StringBuilder output, byte[] bytes, string fieldName)
|
|
{
|
|
// Check for a common case
|
|
if (bytes.Length == 0) return;
|
|
|
|
bool printable = true;
|
|
|
|
for (int i = 0; i < bytes.Length; ++i)
|
|
{
|
|
// Check if there are any unprintable characters in the array
|
|
if ((bytes[i] < 0x20 || bytes[i] > 0x7E) && bytes[i] != 0x09
|
|
&& bytes[i] != 0x0D && bytes[i] != 0x0A && bytes[i] != 0x00)
|
|
{
|
|
printable = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (printable)
|
|
{
|
|
if (fieldName.Length > 0)
|
|
{
|
|
output.Append(fieldName);
|
|
output.Append(": ");
|
|
}
|
|
|
|
if (bytes[bytes.Length - 1] == 0x00)
|
|
output.Append(UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length - 1));
|
|
else
|
|
output.Append(UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length));
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < bytes.Length; i += 16)
|
|
{
|
|
if (i != 0)
|
|
output.Append('\n');
|
|
if (fieldName.Length > 0)
|
|
{
|
|
output.Append(fieldName);
|
|
output.Append(": ");
|
|
}
|
|
|
|
for (int j = 0; j < 16; j++)
|
|
{
|
|
if ((i + j) < bytes.Length)
|
|
output.Append(String.Format("{0:X2} ", bytes[i + j]));
|
|
else
|
|
output.Append(" ");
|
|
}
|
|
|
|
for (int j = 0; j < 16 && (i + j) < bytes.Length; j++)
|
|
{
|
|
if (bytes[i + j] >= 0x20 && bytes[i + j] < 0x7E)
|
|
output.Append((char)bytes[i + j]);
|
|
else
|
|
output.Append(".");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a byte array to a string containing hexadecimal characters
|
|
/// </summary>
|
|
/// <param name="bytes">The byte array to convert to a string</param>
|
|
/// <param name="fieldName">The name of the field to prepend to each
|
|
/// line of the string</param>
|
|
/// <returns>A string containing hexadecimal characters on multiple
|
|
/// lines. Each line is prepended with the field name</returns>
|
|
public static string FieldToHexString(byte[] bytes, string fieldName)
|
|
{
|
|
StringBuilder output = new StringBuilder();
|
|
|
|
for (int i = 0; i < bytes.Length; i += 16)
|
|
{
|
|
if (i != 0)
|
|
output.Append('\n');
|
|
|
|
if (fieldName.Length > 0)
|
|
{
|
|
output.Append(fieldName);
|
|
output.Append(": ");
|
|
}
|
|
|
|
for (int j = 0; j < 16; j++)
|
|
{
|
|
if ((i + j) < bytes.Length)
|
|
output.Append(String.Format("{0:X2} ", bytes[i + j]));
|
|
else
|
|
output.Append(" ");
|
|
}
|
|
|
|
for (int j = 0; j < 16 && (i + j) < bytes.Length; j++)
|
|
{
|
|
if (bytes[i + j] >= 0x20 && bytes[i + j] < 0x7E)
|
|
output.Append(bytes[i + j]);
|
|
else
|
|
output.Append(".");
|
|
}
|
|
}
|
|
|
|
return output.ToString();
|
|
}
|
|
|
|
///// <summary>
|
|
///// Converts a string containing hexadecimal characters to a byte array
|
|
///// </summary>
|
|
///// <param name="hexString">String containing hexadecimal characters</param>
|
|
///// <returns>The converted byte array</returns>
|
|
//public static byte[] HexStringToField(string hexString)
|
|
//{
|
|
// string newString = "";
|
|
// char c;
|
|
|
|
// // FIXME: For each line of the string, if a colon is found
|
|
// // remove everything before it
|
|
|
|
// // remove all non A-F, 0-9, characters
|
|
// for (int i = 0; i < hexString.Length; i++)
|
|
// {
|
|
// c = hexString[i];
|
|
// if (IsHexDigit(c))
|
|
// newString += c;
|
|
// }
|
|
|
|
// // if odd number of characters, discard last character
|
|
// if (newString.Length % 2 != 0)
|
|
// {
|
|
// newString = newString.Substring(0, newString.Length - 1);
|
|
// }
|
|
|
|
// int byteLength = newString.Length / 2;
|
|
// byte[] bytes = new byte[byteLength];
|
|
// string hex;
|
|
// int j = 0;
|
|
// for (int i = 0; i < bytes.Length; i++)
|
|
// {
|
|
// hex = new String(new Char[] { newString[j], newString[j + 1] });
|
|
// bytes[i] = HexToByte(hex);
|
|
// j = j + 2;
|
|
// }
|
|
// return bytes;
|
|
//}
|
|
|
|
/// <summary>
|
|
/// Returns the string between and exclusive of two search characters
|
|
/// </summary>
|
|
/// <param name="src">Source string</param>
|
|
/// <param name="start">Beginning and exclusive of the substring</param>
|
|
/// <param name="end">End and exclusive of the substring</param>
|
|
/// <returns>Substring between the start and end characters</returns>
|
|
public static string StringBetween(string src, char start, char end)
|
|
{
|
|
string ret = String.Empty;
|
|
int idxStart = src.IndexOf(start);
|
|
if (idxStart != -1)
|
|
{
|
|
++idxStart;
|
|
int idxEnd = src.IndexOf(end, idxStart);
|
|
if (idxEnd != -1)
|
|
{
|
|
ret = src.Substring(idxStart, idxEnd - idxStart);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a string to a UTF8 encoded byte array
|
|
/// </summary>
|
|
/// <param name="str">The string to convert</param>
|
|
/// <returns>A null-terminated UTF8 byte array</returns>
|
|
public static byte[] StringToField(string str)
|
|
{
|
|
if (str.Length == 0) { return new byte[0]; }
|
|
if (!str.EndsWith("\0")) { str += "\0"; }
|
|
return System.Text.UTF8Encoding.UTF8.GetBytes(str);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a unix timestamp for the current time
|
|
/// </summary>
|
|
/// <returns>An unsigned integer representing a unix timestamp for now</returns>
|
|
public static uint GetUnixTime()
|
|
{
|
|
return (uint)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a UNIX timestamp to a native DateTime object
|
|
/// </summary>
|
|
/// <param name="timestamp">An unsigned integer representing a UNIX
|
|
/// timestamp</param>
|
|
/// <returns>A DateTime object containing the same time specified in
|
|
/// the given timestamp</returns>
|
|
public static DateTime UnixTimeToDateTime(uint timestamp)
|
|
{
|
|
System.DateTime dateTime = Epoch;
|
|
|
|
// Add the number of seconds in our UNIX timestamp
|
|
dateTime = dateTime.AddSeconds(timestamp);
|
|
|
|
return dateTime;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a UNIX timestamp to a native DateTime object
|
|
/// </summary>
|
|
/// <param name="timestamp">A signed integer representing a UNIX
|
|
/// timestamp</param>
|
|
/// <returns>A DateTime object containing the same time specified in
|
|
/// the given timestamp</returns>
|
|
public static DateTime UnixTimeToDateTime(int timestamp)
|
|
{
|
|
#if PocketPC
|
|
System.DateTime dateTime = Epoch;
|
|
|
|
// Add the number of seconds in our UNIX timestamp
|
|
dateTime = dateTime.AddSeconds(timestamp);
|
|
|
|
return dateTime;
|
|
#else
|
|
return DateTime.FromBinary(timestamp);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a native DateTime object to a UNIX timestamp
|
|
/// </summary>
|
|
/// <param name="time">A DateTime object you want to convert to a
|
|
/// timestamp</param>
|
|
/// <returns>An unsigned integer representing a UNIX timestamp</returns>
|
|
public static uint DateTimeToUnixTime(DateTime time)
|
|
{
|
|
TimeSpan ts = (time - new DateTime(1970, 1, 1, 0, 0, 0));
|
|
return (uint)ts.TotalSeconds;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Swap two values
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of the values to swap</typeparam>
|
|
/// <param name="lhs">First value</param>
|
|
/// <param name="rhs">Second value</param>
|
|
public static void Swap<T>(ref T lhs, ref T rhs)
|
|
{
|
|
T temp = lhs;
|
|
lhs = rhs;
|
|
rhs = temp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test if a single precision float is a finite number
|
|
/// </summary>
|
|
public static bool IsFinite(float value)
|
|
{
|
|
return !(Single.IsNaN(value) || Single.IsInfinity(value));
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="val"></param>
|
|
/// <returns></returns>
|
|
public static int Round(float val)
|
|
{
|
|
return (int)Math.Floor(val + 0.5f);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="a"></param>
|
|
/// <param name="b"></param>
|
|
/// <param name="u"></param>
|
|
/// <returns></returns>
|
|
public static float Lerp(float a, float b, float u)
|
|
{
|
|
return a + ((b - a) * u);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="a"></param>
|
|
/// <param name="b"></param>
|
|
/// <param name="u"></param>
|
|
/// <returns></returns>
|
|
public static LLVector3 Lerp(LLVector3 a, LLVector3 b, float u)
|
|
{
|
|
return new LLVector3(
|
|
a.X + (b.X - a.X) * u,
|
|
a.Y + (b.Y - a.Y) * u,
|
|
a.Z + (b.Z - a.Z) * u);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decode a zerocoded byte array, used to decompress packets marked
|
|
/// with the zerocoded flag
|
|
/// </summary>
|
|
/// <remarks>Any time a zero is encountered, the next byte is a count
|
|
/// of how many zeroes to expand. One zero is encoded with 0x00 0x01,
|
|
/// two zeroes is 0x00 0x02, three zeroes is 0x00 0x03, etc. The
|
|
/// first four bytes are copied directly to the output buffer.
|
|
/// </remarks>
|
|
/// <param name="src">The byte array to decode</param>
|
|
/// <param name="srclen">The length of the byte array to decode. This
|
|
/// would be the length of the packet up to (but not including) any
|
|
/// appended ACKs</param>
|
|
/// <param name="dest">The output byte array to decode to</param>
|
|
/// <returns>The length of the output buffer</returns>
|
|
public static int ZeroDecode(byte[] src, int srclen, byte[] dest)
|
|
{
|
|
uint zerolen = 0;
|
|
int bodylen = 0;
|
|
uint i = 0;
|
|
|
|
try
|
|
{
|
|
Buffer.BlockCopy(src, 0, dest, 0, 6);
|
|
zerolen = 6;
|
|
bodylen = srclen;
|
|
|
|
for (i = zerolen; i < bodylen; i++)
|
|
{
|
|
if (src[i] == 0x00)
|
|
{
|
|
for (byte j = 0; j < src[i + 1]; j++)
|
|
{
|
|
dest[zerolen++] = 0x00;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
dest[zerolen++] = src[i];
|
|
}
|
|
}
|
|
|
|
// Copy appended ACKs
|
|
for (; i < srclen; i++)
|
|
{
|
|
dest[zerolen++] = src[i];
|
|
}
|
|
|
|
return (int)zerolen;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine("Zerodecoding error: " + Helpers.NewLine +
|
|
"i=" + i + "srclen=" + srclen + ", bodylen=" + bodylen + ", zerolen=" + zerolen + Helpers.NewLine +
|
|
FieldToHexString(src, "src") + Helpers.NewLine +
|
|
e.ToString());
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decode enough of a byte array to get the packet ID. Data before and
|
|
/// after the packet ID is undefined.
|
|
/// </summary>
|
|
/// <param name="src">The byte array to decode</param>
|
|
/// <param name="dest">The output byte array to encode to</param>
|
|
public static void ZeroDecodeCommand(byte[] src, byte[] dest)
|
|
{
|
|
for (int srcPos = 6, destPos = 6; destPos < 10; ++srcPos)
|
|
{
|
|
if (src[srcPos] == 0x00)
|
|
{
|
|
for (byte j = 0; j < src[srcPos + 1]; ++j)
|
|
{
|
|
dest[destPos++] = 0x00;
|
|
}
|
|
|
|
++srcPos;
|
|
}
|
|
else
|
|
{
|
|
dest[destPos++] = src[srcPos];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encode a byte array with zerocoding. Used to compress packets marked
|
|
/// with the zerocoded flag. Any zeroes in the array are compressed down
|
|
/// to a single zero byte followed by a count of how many zeroes to expand
|
|
/// out. A single zero becomes 0x00 0x01, two zeroes becomes 0x00 0x02,
|
|
/// three zeroes becomes 0x00 0x03, etc. The first four bytes are copied
|
|
/// directly to the output buffer.
|
|
/// </summary>
|
|
/// <param name="src">The byte array to encode</param>
|
|
/// <param name="srclen">The length of the byte array to encode</param>
|
|
/// <param name="dest">The output byte array to encode to</param>
|
|
/// <returns>The length of the output buffer</returns>
|
|
public static int ZeroEncode(byte[] src, int srclen, byte[] dest)
|
|
{
|
|
uint zerolen = 0;
|
|
byte zerocount = 0;
|
|
|
|
Buffer.BlockCopy(src, 0, dest, 0, 6);
|
|
zerolen += 6;
|
|
|
|
int bodylen;
|
|
if ((src[0] & MSG_APPENDED_ACKS) == 0)
|
|
{
|
|
bodylen = srclen;
|
|
}
|
|
else
|
|
{
|
|
bodylen = srclen - src[srclen - 1] * 4 - 1;
|
|
}
|
|
|
|
uint i;
|
|
for (i = zerolen; i < bodylen; i++)
|
|
{
|
|
if (src[i] == 0x00)
|
|
{
|
|
zerocount++;
|
|
|
|
if (zerocount == 0)
|
|
{
|
|
dest[zerolen++] = 0x00;
|
|
dest[zerolen++] = 0xff;
|
|
zerocount++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (zerocount != 0)
|
|
{
|
|
dest[zerolen++] = 0x00;
|
|
dest[zerolen++] = (byte)zerocount;
|
|
zerocount = 0;
|
|
}
|
|
|
|
dest[zerolen++] = src[i];
|
|
}
|
|
}
|
|
|
|
if (zerocount != 0)
|
|
{
|
|
dest[zerolen++] = 0x00;
|
|
dest[zerolen++] = (byte)zerocount;
|
|
}
|
|
|
|
// copy appended ACKs
|
|
for (; i < srclen; i++)
|
|
{
|
|
dest[zerolen++] = src[i];
|
|
}
|
|
|
|
return (int)zerolen;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the CRC (cyclic redundancy check) needed to upload inventory.
|
|
/// </summary>
|
|
/// <param name="creationDate">Creation date</param>
|
|
/// <param name="saleType">Sale type</param>
|
|
/// <param name="invType">Inventory type</param>
|
|
/// <param name="type">Type</param>
|
|
/// <param name="assetID">Asset ID</param>
|
|
/// <param name="groupID">Group ID</param>
|
|
/// <param name="salePrice">Sale price</param>
|
|
/// <param name="ownerID">Owner ID</param>
|
|
/// <param name="creatorID">Creator ID</param>
|
|
/// <param name="itemID">Item ID</param>
|
|
/// <param name="folderID">Folder ID</param>
|
|
/// <param name="everyoneMask">Everyone mask (permissions)</param>
|
|
/// <param name="flags">Flags</param>
|
|
/// <param name="nextOwnerMask">Next owner mask (permissions)</param>
|
|
/// <param name="groupMask">Group mask (permissions)</param>
|
|
/// <param name="ownerMask">Owner mask (permisions)</param>
|
|
/// <returns>The calculated CRC</returns>
|
|
public static uint InventoryCRC(int creationDate, byte saleType, sbyte invType, sbyte type,
|
|
LLUUID assetID, LLUUID groupID, int salePrice, LLUUID ownerID, LLUUID creatorID,
|
|
LLUUID itemID, LLUUID folderID, uint everyoneMask, uint flags, uint nextOwnerMask,
|
|
uint groupMask, uint ownerMask)
|
|
{
|
|
uint CRC = 0;
|
|
|
|
// IDs
|
|
CRC += assetID.CRC(); // AssetID
|
|
CRC += folderID.CRC(); // FolderID
|
|
CRC += itemID.CRC(); // ItemID
|
|
|
|
// Permission stuff
|
|
CRC += creatorID.CRC(); // CreatorID
|
|
CRC += ownerID.CRC(); // OwnerID
|
|
CRC += groupID.CRC(); // GroupID
|
|
|
|
// CRC += another 4 words which always seem to be zero -- unclear if this is a LLUUID or what
|
|
CRC += ownerMask;
|
|
CRC += nextOwnerMask;
|
|
CRC += everyoneMask;
|
|
CRC += groupMask;
|
|
|
|
// The rest of the CRC fields
|
|
CRC += flags; // Flags
|
|
CRC += (uint)invType; // InvType
|
|
CRC += (uint)type; // Type
|
|
CRC += (uint)creationDate; // CreationDate
|
|
CRC += (uint)salePrice; // SalePrice
|
|
CRC += (uint)((uint)saleType * 0x07073096); // SaleType
|
|
|
|
return CRC;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the MD5 hash of a given string
|
|
/// </summary>
|
|
/// <param name="password">The password to hash</param>
|
|
/// <returns>An MD5 hash in string format, with $1$ prepended</returns>
|
|
public static string MD5(string password)
|
|
{
|
|
StringBuilder digest = new StringBuilder();
|
|
byte[] hash;
|
|
lock(MD5Builder) hash = MD5Builder.ComputeHash(ASCIIEncoding.Default.GetBytes(password));
|
|
|
|
// Convert the hash to a hex string
|
|
foreach (byte b in hash)
|
|
{
|
|
digest.AppendFormat(Helpers.EnUsCulture, "{0:x2}", b);
|
|
}
|
|
|
|
return "$1$" + digest.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to load a file embedded in the assembly
|
|
/// </summary>
|
|
/// <param name="resourceName">The filename of the resource to load</param>
|
|
/// <returns>A Stream for the requested file, or null if the resource
|
|
/// was not successfully loaded</returns>
|
|
public static System.IO.Stream GetResourceStream(string resourceName)
|
|
{
|
|
return GetResourceStream(resourceName, Settings.RESOURCE_DIR);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to load a file either embedded in the assembly or found in
|
|
/// a given search path
|
|
/// </summary>
|
|
/// <param name="resourceName">The filename of the resource to load</param>
|
|
/// <param name="searchPath">An optional path that will be searched if
|
|
/// the asset is not found embedded in the assembly</param>
|
|
/// <returns>A Stream for the requested file, or null if the resource
|
|
/// was not successfully loaded</returns>
|
|
public static System.IO.Stream GetResourceStream(string resourceName, string searchPath)
|
|
{
|
|
try
|
|
{
|
|
System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly();
|
|
System.IO.Stream s = a.GetManifestResourceStream("libsecondlife.Resources." + resourceName);
|
|
if (s != null) return s;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Failed to load the resource from the assembly itself
|
|
}
|
|
|
|
try
|
|
{
|
|
return new System.IO.FileStream(searchPath + System.IO.Path.DirectorySeparatorChar + resourceName,
|
|
System.IO.FileMode.Open);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Failed to load the resource from the given path
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current running platform
|
|
/// </summary>
|
|
/// <returns>Enumeration of the current platform we are running on</returns>
|
|
public static Platform GetRunningPlatform()
|
|
{
|
|
const string OSX_CHECK_FILE = "/Library/Extensions.kextcache";
|
|
|
|
if (Environment.OSVersion.Platform == PlatformID.WinCE)
|
|
{
|
|
return Platform.WindowsCE;
|
|
}
|
|
else
|
|
{
|
|
int plat = (int)Environment.OSVersion.Platform;
|
|
|
|
if ((plat != 4) && (plat != 128))
|
|
{
|
|
return Platform.Windows;
|
|
}
|
|
else
|
|
{
|
|
if (System.IO.File.Exists(OSX_CHECK_FILE))
|
|
return Platform.OSX;
|
|
else
|
|
return Platform.Linux;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a list of primitives to an object that can be serialized
|
|
/// with the LLSD system
|
|
/// </summary>
|
|
/// <param name="prims">Primitives to convert to a serializable object</param>
|
|
/// <returns>An object that can be serialized with LLSD</returns>
|
|
public static StructuredData.LLSD PrimListToLLSD(List<Primitive> prims)
|
|
{
|
|
StructuredData.LLSDMap map = new libsecondlife.StructuredData.LLSDMap(prims.Count);
|
|
|
|
for (int i = 0; i < prims.Count; i++)
|
|
map.Add(prims[i].LocalID.ToString(), prims[i].ToLLSD());
|
|
|
|
return map;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes LLSD in to a list of primitives
|
|
/// </summary>
|
|
/// <param name="llsd">Structure holding the serialized primitive list,
|
|
/// must be of the LLSDMap type</param>
|
|
/// <returns>A list of deserialized primitives</returns>
|
|
public static List<Primitive> LLSDToPrimList(StructuredData.LLSD llsd)
|
|
{
|
|
if (llsd.Type != StructuredData.LLSDType.Map)
|
|
throw new ArgumentException("LLSD must be in the Map structure");
|
|
|
|
StructuredData.LLSDMap map = (StructuredData.LLSDMap)llsd;
|
|
List<Primitive> prims = new List<Primitive>(map.Count);
|
|
|
|
foreach (KeyValuePair<string, StructuredData.LLSD> kvp in map)
|
|
{
|
|
Primitive prim = Primitive.FromLLSD(kvp.Value);
|
|
prim.LocalID = UInt32.Parse(kvp.Key);
|
|
prims.Add(prim);
|
|
}
|
|
|
|
return prims;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Looks up parcel localID from ParcelTracker Dictionary based on parcel bounding box.
|
|
/// </summary>
|
|
/// <param name="sim">Simulator parcel is in</param>
|
|
/// <param name="location">llVector3 location</param>
|
|
/// <returns>parcel localID, 0 = not found</returns>
|
|
/// <remarks>Bounding box is defined by parcel landing point.</remarks>
|
|
[Obsolete("Replaced by ParcelManager.GetParcelLocalID")]
|
|
public static int VecToParcelLocalID(Simulator sim, LLVector3 location)
|
|
{
|
|
int localID = 0;
|
|
sim.Parcels.ForEach(delegate(Parcel parcel)
|
|
{
|
|
if (location.X <= parcel.AABBMax.X &&
|
|
location.X >= parcel.AABBMin.X &&
|
|
location.Y <= parcel.AABBMax.Y &&
|
|
location.Y >= parcel.AABBMin.Y)
|
|
{
|
|
// FIXME: This only checks the AABB of parcels. For non-rectangular parcels
|
|
// it will return random results
|
|
localID = parcel.LocalID;
|
|
}
|
|
});
|
|
|
|
return localID;
|
|
}
|
|
|
|
public static AttachmentPoint StateToAttachmentPoint(uint state)
|
|
{
|
|
const uint ATTACHMENT_MASK = 0xF0;
|
|
uint fixedState = (((byte)state & ATTACHMENT_MASK) >> 4) | (((byte)state & ~ATTACHMENT_MASK) << 4);
|
|
return (AttachmentPoint)fixedState;
|
|
}
|
|
|
|
#region Platform Helper Functions
|
|
|
|
public static bool TryParse(string s, out DateTime result)
|
|
{
|
|
#if PocketPC
|
|
try { result = DateTime.Parse(s); return true; }
|
|
catch (FormatException) { result = Helpers.Epoch; return false; }
|
|
#else
|
|
return DateTime.TryParse(s, out result);
|
|
#endif
|
|
}
|
|
|
|
public static bool TryParse(string s, out int result)
|
|
{
|
|
#if PocketPC
|
|
try { result = Int32.Parse(s); return true; }
|
|
catch (FormatException) { result = 0; return false; }
|
|
#else
|
|
return Int32.TryParse(s, out result);
|
|
#endif
|
|
}
|
|
|
|
public static bool TryParse(string s, out uint result)
|
|
{
|
|
#if PocketPC
|
|
try { result = UInt32.Parse(s); return true; }
|
|
catch (FormatException) { result = 0; return false; }
|
|
#else
|
|
return UInt32.TryParse(s, out result);
|
|
#endif
|
|
}
|
|
|
|
public static bool TryParse(string s, out ulong result)
|
|
{
|
|
#if PocketPC
|
|
try { result = UInt64.Parse(s); return true; }
|
|
catch (FormatException) { result = 0; return false; }
|
|
#else
|
|
return UInt64.TryParse(s, out result);
|
|
#endif
|
|
}
|
|
|
|
public static bool TryParse(string s, out float result)
|
|
{
|
|
#if PocketPC
|
|
try { result = Single.Parse(s, System.Globalization.NumberStyles.Float, Helpers.EnUsCulture.NumberFormat); return true; }
|
|
catch (FormatException) { result = 0f; return false; }
|
|
#else
|
|
return Single.TryParse(s, System.Globalization.NumberStyles.Float, Helpers.EnUsCulture.NumberFormat, out result);
|
|
#endif
|
|
}
|
|
|
|
public static bool TryParse(string s, out double result)
|
|
{
|
|
#if PocketPC
|
|
try { result = Double.Parse(s, System.Globalization.NumberStyles.Float, Helpers.EnUsCulture.NumberFormat); return true; }
|
|
catch (FormatException) { result = 0d; return false; }
|
|
#else
|
|
return Double.TryParse(s, System.Globalization.NumberStyles.Float, Helpers.EnUsCulture.NumberFormat, out result);
|
|
#endif
|
|
}
|
|
|
|
public static bool TryParse(string s, out System.Net.IPAddress result)
|
|
{
|
|
#if PocketPC
|
|
try { result = System.Net.IPAddress.Parse(s); return true; }
|
|
catch (FormatException) { result = System.Net.IPAddress.Loopback; return false; }
|
|
#else
|
|
return System.Net.IPAddress.TryParse(s, out result);
|
|
#endif
|
|
}
|
|
|
|
public static bool TryParse(string s, out LLUUID result)
|
|
{
|
|
return LLUUID.TryParse(s, out result);
|
|
}
|
|
|
|
public static bool TryParse(string s, out LLVector3 result)
|
|
{
|
|
return LLVector3.TryParse(s, out result);
|
|
}
|
|
|
|
public static bool TryParseHex(string s, out uint result)
|
|
{
|
|
#if PocketPC
|
|
try { result = UInt32.Parse(s, System.Globalization.NumberStyles.HexNumber, Helpers.EnUsCulture.NumberFormat); return true; }
|
|
catch (FormatException) { result = 0; return false; }
|
|
#else
|
|
return UInt32.TryParse(s, System.Globalization.NumberStyles.HexNumber, Helpers.EnUsCulture.NumberFormat, out result);
|
|
#endif
|
|
}
|
|
|
|
public static bool StringContains(string haystack, string needle)
|
|
{
|
|
#if PocketPC
|
|
return (haystack.IndexOf(needle) != -1);
|
|
#else
|
|
return haystack.Contains(needle);
|
|
#endif
|
|
}
|
|
|
|
public static bool StringSplitAt(string input, string separator, out string before, out string after)
|
|
{
|
|
int index = input.IndexOf(separator);
|
|
|
|
if (index != -1)
|
|
{
|
|
before = input.Substring(0, index);
|
|
after = input.Substring(index);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
before = String.Empty;
|
|
after = String.Empty;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endregion Platform Helper Functions
|
|
}
|
|
}
|