/* * Copyright (c) 2006-2014, 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.Text; using OpenMetaverse.Packets; using System.IO; using System.Reflection; using OpenMetaverse.StructuredData; using ComponentAce.Compression.Libs.zlib; namespace OpenMetaverse { /// /// Static helper functions and global variables /// public static class Helpers { /// This header flag signals that ACKs are appended to the packet public const byte MSG_APPENDED_ACKS = 0x10; /// This header flag signals that this packet has been sent before public const byte MSG_RESENT = 0x20; /// This header flags signals that an ACK is expected for this packet public const byte MSG_RELIABLE = 0x40; /// This header flag signals that the message is compressed using zerocoding public const byte MSG_ZEROCODED = 0x80; /// /// Passed to Logger.Log() to identify the severity of a log entry /// public enum LogLevel { /// No logging information will be output None, /// Non-noisy useful information, may be helpful in /// debugging a problem Info, /// A non-critical error occurred. A warning will not /// prevent the rest of the library from operating as usual, /// although it may be indicative of an underlying issue Warning, /// A critical error has occurred. Generally this will /// be followed by the network layer shutting down, although the /// stability of the library after an error is uncertain Error, /// 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. /// Debug }; /// /// /// /// /// public static short TEOffsetShort(float offset) { offset = Utils.Clamp(offset, -1.0f, 1.0f); offset *= 32767.0f; return (short)Math.Round(offset); } /// /// /// /// /// /// public static float TEOffsetFloat(byte[] bytes, int pos) { float offset = (float)BitConverter.ToInt16(bytes, pos); return offset / 32767.0f; } /// /// /// /// /// public static short TERotationShort(float rotation) { const float TWO_PI = 6.283185307179586476925286766559f; return (short)Math.Round(((Math.IEEERemainder(rotation, TWO_PI) / TWO_PI) * 32768.0f) + 0.5f); } /// /// /// /// /// /// public static float TERotationFloat(byte[] bytes, int pos) { const float TWO_PI = 6.283185307179586476925286766559f; return ((float)(bytes[pos] | (bytes[pos + 1] << 8)) / 32768.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; } /// /// 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 /// /// The absolute X location, a number such as /// 255360.35 /// The absolute Y location, a number such as /// 255360.35 /// The sim-local X position of the global X /// position, a value from 0.0 to 256.0 /// The sim-local Y position of the global Y /// position, a value from 0.0 to 256.0 /// A 64-bit region handle that can be used to teleport to 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 Utils.UIntsToLong(x, y); } /// /// Converts a floating point number to a terse string format used for /// transmitting numbers in wearable asset files /// /// Floating point number to convert to a string /// A terse string representation of the input number public static string FloatToTerseString(float val) { string s = string.Format(Utils.EnUsCulture, "{0:.00}", val); if (val == 0) return ".00"; // 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; } /// /// Convert a variable length field (byte array) to a string, with a /// field name prepended to each line of the output /// /// If the byte array has unprintable characters in it, a /// hex dump will be written instead /// The StringBuilder object to write to /// The byte array to convert to a string /// A field name to prepend to each line of output 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(" "); } } } } /// /// Decode a zerocoded byte array, used to decompress packets marked /// with the zerocoded flag /// /// 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. /// /// The byte array to decode /// The length of the byte array to decode. This /// would be the length of the packet up to (but not including) any /// appended ACKs /// The output byte array to decode to /// The length of the output buffer public static int ZeroDecode(byte[] src, int srclen, byte[] dest) { if (srclen > src.Length) throw new ArgumentException("srclen cannot be greater than src.Length"); 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 ex) { Logger.Log(String.Format("Zerodecoding error: i={0}, srclen={1}, bodylen={2}, zerolen={3}\n{4}\n{5}", i, srclen, bodylen, zerolen, Utils.BytesToHexString(src, srclen, null), ex), LogLevel.Error); throw new IndexOutOfRangeException(String.Format("Zerodecoding error: i={0}, srclen={1}, bodylen={2}, zerolen={3}\n{4}\n{5}", i, srclen, bodylen, zerolen, Utils.BytesToHexString(src, srclen, null), ex.InnerException)); } } /// /// 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. /// /// The byte array to encode /// The length of the byte array to encode /// The output byte array to encode to /// The length of the output buffer 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; } /// /// Calculates the CRC (cyclic redundancy check) needed to upload inventory. /// /// Creation date /// Sale type /// Inventory type /// Type /// Asset ID /// Group ID /// Sale price /// Owner ID /// Creator ID /// Item ID /// Folder ID /// Everyone mask (permissions) /// Flags /// Next owner mask (permissions) /// Group mask (permissions) /// Owner mask (permissions) /// The calculated CRC public static uint InventoryCRC(int creationDate, byte saleType, sbyte invType, sbyte type, UUID assetID, UUID groupID, int salePrice, UUID ownerID, UUID creatorID, UUID itemID, UUID 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 UUID 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; } /// /// Attempts to load a file embedded in the assembly /// /// The filename of the resource to load /// A Stream for the requested file, or null if the resource /// was not successfully loaded public static System.IO.Stream GetResourceStream(string resourceName) { return GetResourceStream(resourceName, "openmetaverse_data"); } /// /// Attempts to load a file either embedded in the assembly or found in /// a given search path /// /// The filename of the resource to load /// An optional path that will be searched if /// the asset is not found embedded in the assembly /// A Stream for the requested file, or null if the resource /// was not successfully loaded public static System.IO.Stream GetResourceStream(string resourceName, string searchPath) { if (searchPath != null) { Assembly gea = Assembly.GetEntryAssembly(); if (gea == null) gea = typeof(Helpers).Assembly; string dirname = "."; if (gea != null && gea.Location != null) { dirname = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(gea.Location), searchPath); } string filename = System.IO.Path.Combine(dirname, resourceName); try { return new System.IO.FileStream( filename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read); } catch (Exception ex) { Logger.Log(string.Format("Failed opening resource from file {0}: {1}", filename, ex.Message), LogLevel.Error); } } else { try { System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly(); System.IO.Stream s = a.GetManifestResourceStream("OpenMetaverse.Resources." + resourceName); if (s != null) return s; } catch (Exception ex) { Logger.Log(string.Format("Failed opening resource stream: {0}", ex.Message), LogLevel.Error); } } return null; } /// /// Converts a list of primitives to an object that can be serialized /// with the LLSD system /// /// Primitives to convert to a serializable object /// An object that can be serialized with LLSD public static StructuredData.OSD PrimListToOSD(List prims) { StructuredData.OSDMap map = new OpenMetaverse.StructuredData.OSDMap(prims.Count); for (int i = 0; i < prims.Count; i++) map.Add(prims[i].LocalID.ToString(), prims[i].GetOSD()); return map; } /// /// Deserializes OSD in to a list of primitives /// /// Structure holding the serialized primitive list, /// must be of the SDMap type /// A list of deserialized primitives public static List OSDToPrimList(StructuredData.OSD osd) { if (osd.Type != StructuredData.OSDType.Map) throw new ArgumentException("LLSD must be in the Map structure"); StructuredData.OSDMap map = (StructuredData.OSDMap)osd; List prims = new List(map.Count); foreach (KeyValuePair kvp in map) { Primitive prim = Primitive.FromOSD(kvp.Value); prim.LocalID = UInt32.Parse(kvp.Key); prims.Add(prim); } return prims; } /// /// Converts a struct or class object containing fields only into a key value separated string /// /// The struct object /// A string containing the struct fields as the keys, and the field value as the value separated /// /// /// // Add the following code to any struct or class containing only fields to override the ToString() /// // method to display the values of the passed object /// /// /// Print the struct data as a string /// ///A string containing the field name, and field value ///public override string ToString() ///{ /// return Helpers.StructToString(this); ///} /// /// public static string StructToString(object t) { StringBuilder result = new StringBuilder(); Type structType = t.GetType(); FieldInfo[] fields = structType.GetFields(); foreach (FieldInfo field in fields) { result.Append(field.Name + ": " + field.GetValue(t) + " "); } result.AppendLine(); return result.ToString().TrimEnd(); } public static void CopyStream(Stream input, Stream output) { byte[] buffer = new byte[4096]; int read; while ((read = input.Read(buffer, 0, buffer.Length)) > 0) { output.Write(buffer, 0, read); } } public static byte[] ZCompressOSD(OSD data) { byte[] ret = null; using (MemoryStream outMemoryStream = new MemoryStream()) using (ZOutputStream outZStream = new ZOutputStream(outMemoryStream, zlibConst.Z_BEST_COMPRESSION)) using (Stream inMemoryStream = new MemoryStream(OSDParser.SerializeLLSDBinary(data, false))) { CopyStream(inMemoryStream, outZStream); outZStream.finish(); ret = outMemoryStream.ToArray(); } return ret; } public static OSD ZDecompressOSD(byte[] data) { OSD ret; using (MemoryStream input = new MemoryStream(data)) using (MemoryStream output = new MemoryStream()) using (ZOutputStream zout = new ZOutputStream(output)) { CopyStream(input, zout); zout.finish(); output.Seek(0, SeekOrigin.Begin); ret = OSDParser.DeserializeLLSDBinary(output); } return ret; } } }