662 lines
25 KiB
C#
662 lines
25 KiB
C#
/*
|
|
* Copyright (c) 2006-2016, openmetaverse.co
|
|
* 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.co 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 System.IO;
|
|
using System.IO.Compression;
|
|
using System.Reflection;
|
|
using OpenMetaverse.StructuredData;
|
|
using ComponentAce.Compression.Libs.zlib;
|
|
|
|
namespace OpenMetaverse
|
|
{
|
|
/// <summary>
|
|
/// Static helper functions and global variables
|
|
/// </summary>
|
|
public static class Helpers
|
|
{
|
|
/// <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>
|
|
/// Passed to Logger.Log() to identify the severity of a log entry
|
|
/// </summary>
|
|
public enum LogLevel
|
|
{
|
|
/// <summary>No logging information will be output</summary>
|
|
None,
|
|
/// <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 the library 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 the library 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>
|
|
///
|
|
/// </summary>
|
|
/// <param name="offset"></param>
|
|
/// <returns></returns>
|
|
public static short TEOffsetShort(float offset)
|
|
{
|
|
offset = Utils.Clamp(offset, -1.0f, 1.0f);
|
|
offset *= 32767.0f;
|
|
return (short)Math.Round(offset);
|
|
}
|
|
|
|
public static IEnumerable<string> SplitBy(this string str, int chunkLength)
|
|
{
|
|
if (string.IsNullOrEmpty(str)) throw new ArgumentException();
|
|
if (chunkLength < 1) throw new ArgumentException();
|
|
|
|
for (int i = 0; i < str.Length; i += chunkLength)
|
|
{
|
|
if (chunkLength + i > str.Length)
|
|
chunkLength = str.Length - i;
|
|
|
|
yield return str.Substring(i, chunkLength);
|
|
}
|
|
}
|
|
|
|
/// <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 float TWO_PI = 6.283185307179586476925286766559f;
|
|
return (short)Math.Round(((Math.IEEERemainder(rotation, TWO_PI) / TWO_PI) * 32768.0f) + 0.5f);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="bytes"></param>
|
|
/// <param name="pos"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <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>
|
|
/// <remarks>
|
|
/// NOTE: this does not work for varregions -- the region handle is correct but
|
|
/// the local X,Y are wrong. TODO: create new function
|
|
/// that takes a reference to the region and thus can calculate region local address.
|
|
/// </remarks>
|
|
/// <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 / Simulator.DefaultRegionSizeX) * Simulator.DefaultRegionSizeX;
|
|
uint y = ((uint)globalY / Simulator.DefaultRegionSizeY) * Simulator.DefaultRegionSizeY;
|
|
localX = globalX - (float)x;
|
|
localY = globalY - (float)y;
|
|
return Utils.UIntsToLong(x, y);
|
|
}
|
|
|
|
/// <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(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;
|
|
}
|
|
|
|
/// <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 ((i < 0x20 || i > 0x7E) && i != 0x09
|
|
&& i != 0x0D && i != 0x0A && i != 0x00)
|
|
{
|
|
printable = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (printable)
|
|
{
|
|
if (fieldName.Length > 0)
|
|
{
|
|
output.Append(fieldName);
|
|
output.Append(": ");
|
|
}
|
|
|
|
output.Append(bytes[bytes.Length - 1] == 0x00
|
|
? Encoding.UTF8.GetString(bytes, 0, bytes.Length - 1)
|
|
: Encoding.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++)
|
|
{
|
|
output.Append((i + j) < bytes.Length ? $"{bytes[i + j]:X2} " : " ");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
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));
|
|
}
|
|
}
|
|
|
|
/// <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 (permissions)</param>
|
|
/// <returns>The calculated CRC</returns>
|
|
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;
|
|
}
|
|
|
|
/// <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, "openmetaverse_data");
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
if (searchPath != null)
|
|
{
|
|
Assembly gea = Assembly.GetEntryAssembly();
|
|
if (gea == null) gea = typeof(Helpers).Assembly;
|
|
string dirname = ".";
|
|
if (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($"Failed opening resource from file {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($"Failed opening resource stream: {ex.Message}", LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
/// <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.OSD PrimListToOSD(List<Primitive> prims)
|
|
{
|
|
StructuredData.OSDMap map = new OpenMetaverse.StructuredData.OSDMap(prims.Count);
|
|
|
|
foreach (Primitive prim in prims)
|
|
map.Add(prim.LocalID.ToString(), prim.GetOSD());
|
|
|
|
return map;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes OSD in to a list of primitives
|
|
/// </summary>
|
|
/// <param name="osd">Structure holding the serialized primitive list,
|
|
/// must be of the SDMap type</param>
|
|
/// <returns>A list of deserialized primitives</returns>
|
|
public static List<Primitive> 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<Primitive> prims = new List<Primitive>(map.Count);
|
|
|
|
foreach (KeyValuePair<string, StructuredData.OSD> kvp in map)
|
|
{
|
|
Primitive prim = Primitive.FromOSD(kvp.Value);
|
|
prim.LocalID = uint.Parse(kvp.Key);
|
|
prims.Add(prim);
|
|
}
|
|
|
|
return prims;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a struct or class object containing fields only into a key value separated string
|
|
/// </summary>
|
|
/// <param name="t">The struct object</param>
|
|
/// <returns>A string containing the struct fields as the keys, and the field value as the value separated</returns>
|
|
/// <example>
|
|
/// <code>
|
|
/// // 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
|
|
///
|
|
/// /// <summary>Print the struct data as a string</summary>
|
|
/// ///<returns>A string containing the field name, and field value</returns>
|
|
///public override string ToString()
|
|
///{
|
|
/// return Helpers.StructToString(this);
|
|
///}
|
|
/// </code>
|
|
/// </example>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// decompresses a gzipped OSD object
|
|
/// </summary>
|
|
/// <param name="meshBytes"></param>
|
|
/// <returns>the OSD object</returns>
|
|
public static OSD DecompressOSD(byte[] meshBytes) {
|
|
OSD decodedOsd = null;
|
|
|
|
using (MemoryStream inMs = new MemoryStream(meshBytes))
|
|
using (MemoryStream outMs = new MemoryStream())
|
|
using (DeflateStream decompressionStream = new DeflateStream(inMs, CompressionMode.Decompress))
|
|
{
|
|
byte[] readBuffer = new byte[2048];
|
|
inMs.Read(readBuffer, 0, 2); // skip first 2 bytes in header
|
|
int readLen = 0;
|
|
|
|
while ((readLen = decompressionStream.Read(readBuffer, 0, readBuffer.Length)) > 0)
|
|
outMs.Write(readBuffer, 0, readLen);
|
|
|
|
outMs.Flush();
|
|
|
|
outMs.Seek(0, SeekOrigin.Begin);
|
|
|
|
byte[] decompressedBuf = outMs.GetBuffer();
|
|
|
|
decodedOsd = OSDParser.DeserializeLLSDBinary(decompressedBuf);
|
|
}
|
|
return decodedOsd;
|
|
}
|
|
|
|
}
|
|
}
|